/*========================================================================
==========================================================================

                    P    E    R    K    M    O    D    2
                    -----------------------------
                        by tPoncho, aka tP

                   I owe a great deal of thanks to:

                            Skorpion1976
                              Uyukio
                            spiderlemur
                                olj
                            grandwazir

                        and a special thanks to
                            AtomicStryker

            and to everyone else in the Sourcemod community
                    for feedback and support! ^^

- version 1.0:
        Initial release
- version 1.0.1:
        Changed code for Blind Luck with Doku's
- version 1.0.2:
        Attempted fix for outrageous health values for survivors.
        Fix for Double Tap not working on team switches.
- version 1.1:
        Replaced Speed Demon with Grasshopper.
        Included CVars to disable perks.
        Fix for various tank perks either not applying or applying too
            many times (double the trouble health multiplier).
        Added more info to perk CVars for min and max values allowed.
        Added in a CVar to disable plugin adjustments to survivor crawling.
        Changing teams to survivors should grant perks.
- version 1.1.1:
        Fixed Martial Artist not resetting fatigue.
        Removed    debug messages (oops!)
- version 1.2:
        New perk for Boomers - Motion Sickness, boosts movement speed and
            lets you run while vomiting.
        Reinstated Speed Demon (for hunters).
        Nerfed Metabolic Boost default value from 1.5 to 1.4.
        Nerfed Tongue Twister default values slightly (pull speed 1.75->1.5,
            shoot speed    1.75->1.5, range 1.75 unchanged).
        Unbreakable now gives bonus buffer on being revived.
        Hopefully fixed problem with unbreakable not applying sometimes.
        Hopefully fixed problem with second tank not spawning with Double
            Trouble.
        Minor fixes for (mostly) harmless server errors
- version 1.3:
        Double Trouble tanks can no longer be frustrated as long as    both
            are alive (band-aid solution to the disappearing-tank-when-both-
            tanks-are-alive-and-one-gets-frustrated problem).
        Pack Rat now gives ammo based on max ammo values set by the server
            instead of absolute values.
        Nerfed Dead Wreckening default value
            (damage multiplier 1.0 -> 0.5).
        Added in an option for players to pick random perks.
        Added in a CVar to force random perks on players every roundstart.
        Revised the show-menu code slightly to always show the initial
            "customize/playNOW!" menu on roundstarts.
        Revised some code so that disabling a perk that changes ConVars
            will make the plugin stop modifying those ConVars.
        Added in ConVars to selectively disable ConVar modifying for the
            perks Helping Hand and Blind Luck.
        Renamed    ConVar "l4d_perkmod_spirit_crawling" to
            "l4d_perkmod_spirit_enable_convarchanges" to keep the naming
            system consistent with the other new ConVar-change-permission
            ConVars.
- version 1.4:
        Revised Spirit so that self-reviving doesn't reset black-and-
            white health (thanks to AtomicStryker!). Self-reviving through
            Spirit won't increase the revive count towards black-and-white
            health,    however.
        Reduced default Spirit cooldown values
            (versus and survival 210s -> 150s, campaign 540s -> 240s).
        Bots now receive random perks. The pool of perks that bots can choose
            from can be adjusted with "l4d_perkmod_bot_<type> <range>".
            General format is "1,2,3,4,5", "3,2,5", etc.
        Added CVars to disable entire perk trees, which will stop all perks
            under that tree from working, and also hide    the perk tree from
            player menus.
        Revised perk code so that non-CVar-adjusting perks will also cease
            functioning if disabled in general.
        Fixed some code that was causing CVar-adjusting perks to adjust CVars
            even when disabled in game modes other than campaign.
        Added CVar to disable the player's option to randomize their perks
            ("l4d_perkmod_randomperks_enable")
        Added missing code to when a player selects "Play NOW!" on the
            initial perk menu, which wasn't applying some perk benefits
            (thanks again, AtomicStryker!)
        Added an OnPluginEnd function.
- version 1.4.1:
        Fixed problems with never-ending music.
        Merged Speed Demon with Old School. Reduced Speed Demon to 1.4x and
            Old School to 3/6/12. CVar names are unchanged.
        Seemed to be some problems with damage adds. Fixed.
- version 1.4.2:
        Fixed PKT errors showing up on clients' console.
- version 2.0.0a:
        Support for L4D2.
        Pack Rat works with L4D2 guns.
- version 2.0.1a:
        Newer, shinier version for L4D2!
        Reorganized Survivor perks into three categories.
        Rebalanced Survivor perk values.
        Martial Artist (remake): now increases melee attack speed.
        Efficient Killer (remake): now deals a flat +50% damage bonus to all damage.
        Modified Pack Rat to also work with grenade launchers.
        Updated Pyrotechnician to include vomit jars.
        Updated Chem Reliant to include adrenaline shots.
        Changed Dead Wreckening to better handle rounding. If rounding error
            can occur (say it calculates 1.5 damage), then it will randomly
            choose either the higher or the lower (1 or 2).
        Fixed an issue with Spirit firing on tank deaths - no, Valve,
            tank deaths do NOT count as being incapped! ^^
        Adrenal Glands no longer increases rock travel speed.
        Spirit no longer allows crawling.
        Added some code so some perks should recognize when a survivor is being
            disabled by a charger (ie., spirit).
        Removed Old School and Speed Demon perks.
        Converted a few perks that reduce cooldowns to not use CVars. Some
            perks still use CVars - namely, Helping Hand (revive time),
            Tongue Twister (everything about it), Drag and Drop (manual
            release, drop-to-ground/recovery time)
        Rewrote some class checks to check actual netprop values instead
            of checking model names.
- version 2.0.2a:
        Fixed cooldown-reducing perks; most had stopped working with the last update.
        Twin Spitfire now activates properly for the first attack after spawning.
        After going AFK (in coop), the menu shouldn't reset perks for the player.
        The menu should appear somewhat more consistently.
        Martial Artist (remake): Now allows the player to swing melee weapons twice
             rapidly in succession, and reduces the maximum shove penalty for any weapon.
        Extreme Conditioning (new): gives a survivor +10% run speed.
- version 2.0.3a:
        Reinstated Speed Demon perk for hunters.
        Fixed Sleight of Hand bug that prevented being able to shoot after reloading.
- version 2.0.4a:
        Added backwards compatibility for L4D1.
        Fixed some major bugs with Spirit. Spirit will no longer attempt to revive a
            player immediately after being incapped, which should increase the reliability
            of the perk functioning, but it means that if the last survivor standing
            gets incapped, even if that survivor or another survivor has spirit, too bad.
        Added some extra checks for Unbreakable to fix some rare abuses.
        Fixed Double Trouble not spawning the second tank.
        Double Trouble no longer stops tanks from becoming frustrated.
- version 2.0.5a:
        Attempted fixes for stability.
- version 2.0.6a:
        Another attempt at fixing stability.
- version 2.0.7a:
        Fixed error messages for Unbreakable and Sleight of Hand.
        Revised Twin Spitfire to be more consistent in giving two shots.
        Increased Helping Hand bonus health from 10 to 15.
        Increased Spirit bonus health from 0 to 10.
        Plugin will now attempt to load values from the .cfg file on round start, in
            addition to loading it on plugin load.
- version 2.0.8a:
        Revised menu code, should properly show a player that a perk is disabled.
        Fixed a bug with Hard to Kill.
- version 2.0.9:
        Added Cavalier perk for Jockeys: gives 60% more health.
        Changed Spirit so that a self-revive counts as a revive - meaning each self-revive
            moves you one step closer to black-and-white health.
        Increased Spirit self-revive health bonus from 10 to 30
        Fixed some bugs with Unbreakable, wasn't giving bonus health buffer on revive or
            after being rescued from the closet.
        Reduced Unbreakable bonus health buffer on revive from 16 to 10 (0.8 -> 0.5 of
            Unbreakable health bonus value).
        Helping Hand now gives bonus health buffer to the reviver as well.
        Reduced Helping Hand bonus buffer from 15 to 10 for versus.
        Lowered Double the Trouble health modifier from 0.6 to 0.45
        Lowered Body Slam minimum pounce damage from 11 to 10
        Lowered Efficient Killer damage bonus from 0.3 to 0.2
        Lowered Grasshopper pounce speed multiplier from 1.3 to 1.2
- version 2.0.10:
        Added Frogger perk for Jockeys: 20% more damage, 30% more leap distance.
        Added Ghost Rider perk for Jockeys: near invisibility.
        Added Speeding Bullet perk for Chargers: charges are 30% faster and longer.
        Modified Scattering Ram to also give 30% more health.
        Revised menu code again, shouldn't display disabled perks.
- version 2.0.11:
        Adrenal Glands now also makes the tank unfrustratable.
        Increased Frogger damage from 0.2 -> 0.35
        Fixed Helping Hand bug that wasn't giving the reviver bonus health buffer.
        Fixed bug/exploit with Chem Reliant/Pyrotechnician/Unbreakable on switching
            from AFK to non-AFK.
        Fixed Pack Rat bug/exploit and updated some old code.
- version 2.0.12:
        The plugin will no longer read from the cfg file on round start,
            was probably causing some buffer overflows...
        Fixed a few minor errors.
- version 2.1.0:
        Twin Spitfire, Adrenal Glands and other cooldown-reducing perks should now
            work properly on Linux servers (nobody told me the offsets were all weird
            between Linux and Windows servers =.=). Still don't have offset numbers for
            L4D1 Linux, so it won't work for L4D1 Linux until I can find those numbers...
        Being rescued from the closet now gives bonus items with the corresponding perks
            (grenades for Pyrotechnician, pills for Chem Reliant).
        Added translations! (basically stole AtomicStryker's work =P)
- version 2.1.1:
        Added Mega Adhesive perk to spitters: slows survivor speed by 50% for up to two seconds
            after leaving spit.
        Added Smoke IT! perk to smokers: can walk while smoking (thanks to Olj for this perk!)
        Added Little Leaguer perk to survivor-tertiary: gives a baseball bat.
        Saying !perks should work more consistently (but perks may still not work properly
            on local servers!)
- version 2.1.2:
        Updated Martial Artist to now give 3 swings instead of 2, and shoving or drawing the
            melee weapon no longer "counts" as a swing.
        Helping Hand should now be properly granting the reviver half the bonus health buffer
            as well (was previously only granting it to the revivee).
- version 2.1.3:
        Double Tap now has a 15% chance per shot that a bullet will not be consumed.
        Rewrote Pack Rat to address multiple bugs.
        Added a CVar "l4d_perkmod_autoshowmenu" to enable or disable automatically showing the
            perks menu on roundstart.
        Updated offsets for ability and attack timers, cooldown-reducing perks (ie. Twin Spitfire)
            and attack speed increase perks (ie. Adrenal Glands) should now work properly.
- version 2.1.4:
        Increased Twin Spitfire delay in-between spits from 2.5s -> 6s.
        Increased Mega-Adhesive slow from 50% -> 60%.
        Updated Pack Rat further, should be less buggy... with any luck.
- version 2.1.5:
        Rewrote some code, Mega Adhesive should no longer interfere with other movement-changing
            infected perks.
        Fixed an exploit with faster reloading for Double Tap.
        Updated Pack Rat again to address more bugs.


==========================================================================
========================================================================*/



//=============================
// Start
//=============================

#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <colors>
#define PLUGIN_VERSION "2.1.5"

//info
public Plugin:myinfo=
{
    name="PerkMod",
    author="tPoncho",
    description="Adds Call Of Duty-style perks for L4D",
    version=PLUGIN_VERSION,
    url=""
}



//=============================
// Declare Global Variables
//=============================

//init player perk vars 玩家技能
new g_iSur1[MAXPLAYERS+1];    //survivors, primary 幸存者3个主要技能
new g_iSur2[MAXPLAYERS+1];    //survivors, secondary
new g_iSur3[MAXPLAYERS+1];    //survivors, tertiary
new g_iInf1[MAXPLAYERS+1];    //boomer 感染者技能
new g_iInf2[MAXPLAYERS+1];    //tank
new g_iConfirm[MAXPLAYERS+1];    //check if perks are confirmed, to prevent mid-game changing abuses
new g_iInf3[MAXPLAYERS+1];    //smoker
new g_iInf4[MAXPLAYERS+1];    //hunter
new g_iInf5[MAXPLAYERS+1];    //jockey
new g_iInf6[MAXPLAYERS+1];    //spitter
new g_iInf7[MAXPLAYERS+1];    //charger

//timer perks handle
new Handle:g_hTimerPerks = INVALID_HANDLE;

//PYROTECHNICIAN PERK 爆破专家技能参数
//track how many grenades are carried for pyrotechnician perk
new g_iGren[MAXPLAYERS+1];
//used so functions don't confuse legitimate grenade pickups
//with acquisitions from grenadier perk
new g_iGrenThrow[MAXPLAYERS+1];
//used to track which type of grenade was used;
//1 = pipe, 2 = molotov
new g_iGrenType[MAXPLAYERS+1];

//SPIRIT PERK 黄金战甲技能参数
//0 = not incapped
//1 = incapped
new g_iPIncap[MAXPLAYERS+1];
//used to keep track of whether cooldown is in effect
new g_iSpiritCooldown[MAXPLAYERS+1];
//used to track the timers themselves
new Handle:g_iSpiritTimer[MAXPLAYERS+1];

//DOUBLE TAP PERK 夺命射速技能参数
//used to track who has the double tap perk
//index goes up to 18, but each index has
//a value indicating a client index with DT
//so the plugin doesn't have to cycle a full
//18 times per game frame
new g_iDTRegisterIndex[MAXPLAYERS+1] = -1;
//and this tracks how many have DT
new g_iDTRegisterCount = 0;
//this tracks the current active weapon id
//in case the player changes guns
new g_iDTEntid[MAXPLAYERS+1] = -1;
//used for the bullet conservation
new g_iDTEntid_firearm[MAXPLAYERS+1] = -1;
//this tracks the engine time of the next
//attack for the weapon, after modification
//(modified interval + engine time)
new Float:g_flDTNextTime[MAXPLAYERS+1] = -1.0;

//SLEIGHT OF HAND PERK 急速上弹技能参数
//this keeps track of the default values for
//reload speeds for the different shotgun types
//NOTE: I got these values over testing earlier
//and since it's a waste of processing time to
//retrieve these values constantly, we just use
//the pre-retrieved values
//NOTE: updated for L4D2, pump and chrome have
//identical values
const Float:g_flSoHAutoS = 0.666666;
const Float:g_flSoHAutoI = 0.4;
const Float:g_flSoHAutoE = 0.675;
const Float:g_flSoHSpasS = 0.5;
const Float:g_flSoHSpasI = 0.375;
const Float:g_flSoHSpasE = 0.699999;
const Float:g_flSoHPumpS = 0.5;
const Float:g_flSoHPumpI = 0.5;
const Float:g_flSoHPumpE = 0.6;

//MARTIAL ARTIST PERK 近战专家技能参数
//similar to Double Tap
new g_iMARegisterIndex[MAXPLAYERS+1] = -1;
//and this tracks how many have MA
new g_iMARegisterCount = 0;
//these are similar to those used by Double Tap
new Float:g_flMANextTime[MAXPLAYERS+1] = -1.0;
new g_iMAEntid[MAXPLAYERS+1] = -1;
new g_iMAEntid_notmelee[MAXPLAYERS+1] = -1;
//this tracks the attack count, similar to twinSF
new g_iMAAttCount[MAXPLAYERS+1] = -1;

//PACK RAT PERK  弹药专家技能参数
//prevents perk from applying multiple times within a short interval
//ie. when two related events fire at the same time that both trigger PR
new bool:g_bPRalreadyApplying[MAXPLAYERS+1] = false;

//VARIOUS INFECTED PERKS 各种感染者技能参数
//this is used by most cooldown-reducing SI
//perks, keeps track of when an ability was used
new Float:g_flTimeStamp[MAXPLAYERS+1] = -1.0;
//contains id of target, for given disabler
new g_iMyDisableTarget[MAXPLAYERS+1] = -1;
//contains id of disabler, for given survivor
new g_iMyDisabler[MAXPLAYERS+1] = -1;
// boomer技能参数
//BARF BAGGED PERK 胖子恶魔唤醒参数
//used to track how many survivors are boomed at a given time
//because spawning a whole mob per player is WAY too many
//also used indirectly to check if someone is currently vomited on
new g_iSlimed=0;

//DEAD WRECKENING PERK  boomer致命袭击参数
//used to track who vomited on a survivor last
new g_iSlimerLast=0;

//TWIN SPITFIRE PERK 口水多重喷射
//similar to Double Tap
new g_iTwinSFShotCount[MAXPLAYERS+1] = 0;

//MEGA ADHESIVE PERK 粘性毒液
new Handle:g_hMegaAdTimer[MAXPLAYERS+1] = INVALID_HANDLE;
new g_iMegaAdCount[MAXPLAYERS+1] = 0;

//TANKS tank技能参数
//tracks whether tanks are existent, and what perks have been given
//0 = no tank;
//1 = tank, but no special perks assigned yet;
//2 = tank, juggernaut has been given;
//3 = tank, double trouble has been given;
//4 = frustrated tank with double trouble is being passed to another player;
new g_iTank=0;
new g_iTankCount=0;        //tracks how many tanks there under double trouble modification
new g_iTankBotTicks=0;    //after 3 ticks, if tank is still a bot then give buffs
new g_iTank_MainId=0;    //tracks which tank is the "original", for Double Trouble
//similar to Double Tap, only used for punches
new g_iAdrenalRegisterCount = 0;
new g_iAdrenalRegisterIndex[MAXPLAYERS+1] = -1;
new Float:g_flAdrenalTimeStamp[MAXPLAYERS+1] = -1.0;
new clientlosscount[MAXPLAYERS+1];  // 丢失tank次数

//VARS TO STORE CONVAR VALUES
//declare revive time var 复活时间
new Float:g_flReviveTime= -1.0;
//declare vomit fatigue var胖子喷吐CD
new Float:g_flVomitFatigue= -1.0;
//declare yoink tongue speed var 舌头速度
new Float:g_flTongueSpeed= -1.0;
new Float:g_flTongueFlySpeed= -1.0;
new Float:g_flTongueRange= -1.0;
//declare drag and drop vars 控舌CD
new Float:g_flTongueDropTime= -1.0;

//OFFSETS 参数
new g_iHPBuffO            = -1;
new g_iHPBuffTimeO        = -1;
new g_iRevCountO        = -1;
new g_iMeleeFatigueO    = -1;
new g_iNextPAttO        = -1;
new g_iNextSAttO        = -1;
new g_iActiveWO            = -1;
new g_iShotStartDurO    = -1;
new g_iShotInsertDurO    = -1;
new g_iShotEndDurO        = -1;
new g_iPlayRateO        = -1;
new g_iShotRelStateO    = -1;
new g_iNextAttO            = -1;
new g_iTimeIdleO        = -1;
new g_iLaggedMovementO    = -1;
new g_iFrustrationO        = -1;
new g_iAbilityO            = -1;
new g_iClassO            = -1;
new g_iVMStartTimeO        = -1;
new g_iViewModelO        = -1;
new g_iIncapO            = -1;
new g_iIsGhostO            = -1;
new g_iClipO            = -1;

//these offsets refuse to be searched for (these netprops
//have unique names, but the SENDTABLE names are not unique - 
//usually DT_CountdownTimer, making it impossible to search
//for AFAIK...), so we'll just declare them here and hope
//Valve doesn't change them...

//these offsets are for L4D2, Windows操作系统参数，Windows不需修改
//next activation timer now +4 since valve update
//-----------------------------
//windows and linux offsets are checked
//during roundstart by comparing an offset
//to known offset numbers
new g_iNextActO = 1068;
new g_iAttackTimerO = 5436;

//these are for L4D2, Linux
//new g_iNextActO_linux = 1088;
//new g_iAttackTimerO_linux = 5444;



//=============================
// Declare Variables that track
// base L4D ConVars
//=============================

//tracks game mode 游戏运行模式，战役，幸存，对抗
//0 = campaign, realism
//1 = survival
//2 = versus, scavenge, team variants
new g_iL4D_GameMode;

//tracks if the game is L4D 1 or 2
new g_iL4D_12 = 0;

//prevents certain functions from spamming too often
new bool:g_bIsRoundStart     = false; // 回合循环开始
new bool:g_bIsLoading         = false; // 正在加载


//=============================
// Declare Variables Related to
// the Plugin's Own ConVars
//=============================
//first line says the name of the perk第一行为技能名称
//second line describes how many types there are技能不同类型描述
//ie:
//"one-size-fits-all" = one variable across all game modes and difficulties可应用于的模式和级别
//"versus, non-versus" = one variable for versus games, one for non-versus games对抗or非对抗
//"normal, hard, expert" = separate variables for normal-versus-survival, advanced and expert普通，高级，专家模式


//SUR1 PERKS                                                    幸存技能1
//stopping power, damage multiplier 枪神
//one-size-fits-all
new Handle:g_hStopping_enable;
new Handle:g_hStopping_enable_sur;
new Handle:g_hStopping_enable_vs;
new Handle:g_hStopping_dmgmult;
//associated var
new g_iStopping_enable;
new g_iStopping_enable_sur;
new g_iStopping_enable_vs;
new Float:g_flStopping_dmgmult;

//spirit, bonus buffer and cooldown黄金战甲
//campaign, survival, versus
new Handle:g_hSpirit_enable;
new Handle:g_hSpirit_enable_sur;
new Handle:g_hSpirit_enable_vs;
new Handle:g_hSpirit_buff;
new Handle:g_hSpirit_cd;
new Handle:g_hSpirit_cd_sur;
new Handle:g_hSpirit_cd_vs;
//associated vars
new g_iSpirit_enable;
new g_iSpirit_enable_sur;
new g_iSpirit_enable_vs;
new g_iSpirit_buff;
new g_iSpirit_cd;
new g_iSpirit_cd_sur;
new g_iSpirit_cd_vs;

//unbreakable, bonus hp          医疗专家
//one-size-fits-all
new Handle:g_hUnbreak_enable;
new Handle:g_hUnbreak_enable_sur;
new Handle:g_hUnbreak_enable_vs;
new Handle:g_hUnbreak_hp;
//associated var
new g_iUnbreak_enable;
new g_iUnbreak_enable_sur;
new g_iUnbreak_enable_vs;
new g_iUnbreak_hp;

//double tap, fire rate             夺命射速
//one-size-fits-all
new Handle:g_hDT_enable;
new Handle:g_hDT_enable_sur;
new Handle:g_hDT_enable_vs;
new Handle:g_hDT_rate;
new Handle:g_hDT_freechance;
//associated var
new g_iDT_enable;
new g_iDT_enable_sur;
new g_iDT_enable_vs;
new Float:g_flDT_rate;
new Float:g_flDT_freechance;

//sleight of hand, reload rate 急速上弹
//one-size-fits-all
new Handle:g_hSoH_enable;
new Handle:g_hSoH_enable_sur;
new Handle:g_hSoH_enable_vs;
new Handle:g_hSoH_rate;
//associated var
new g_iSoH_enable;
new g_iSoH_enable_sur;
new g_iSoH_enable_vs;
new Float:g_flSoH_rate;

//pyrotechnician                    爆破专家
new Handle:g_hPyro_enable;
new Handle:g_hPyro_enable_sur;
new Handle:g_hPyro_enable_vs;
new g_iPyro_enable;
new g_iPyro_enable_sur;
new g_iPyro_enable_vs;


//SUR2 PERKS                                        幸存技能2
//chem reliant, bonus buffer    枪支专家
//one-size-fits-all
new Handle:g_hChem_enable;
new Handle:g_hChem_enable_sur;
new Handle:g_hChem_enable_vs;
new Handle:g_hChem_buff;
//associated var
new g_iChem_enable;
new g_iChem_enable_sur;
new g_iChem_enable_vs;
new g_iChem_buff;

//helping hand, bonus buffer and time multiplier    救护专家
//versus, non-versus
new Handle:g_hHelpHand_enable;
new Handle:g_hHelpHand_enable_sur;
new Handle:g_hHelpHand_enable_vs;
new Handle:g_hHelpHand_convar;
new Handle:g_hHelpHand_timemult;
new Handle:g_hHelpHand_buff;
new Handle:g_hHelpHand_buff_vs;
//associated vars
new g_iHelpHand_enable;
new g_iHelpHand_enable_sur;
new g_iHelpHand_enable_vs;
new g_iHelpHand_convar;
new Float:g_flHelpHand_timemult;
new g_iHelpHand_buff;
new g_iHelpHand_buff_vs;

//pack rat, bonus ammo multiplier       弹药专家
//one-size-fits-all
new Handle:g_hPack_enable;
new Handle:g_hPack_enable_sur;
new Handle:g_hPack_enable_vs;
new Handle:g_hPack_ammomult;
//associated var
new g_iPack_enable;
new g_iPack_enable_sur;
new g_iPack_enable_vs;
new Float:g_flPack_ammomult;

//hard to kill, hp multiplier           坚韧之躯
//one-size-fits-all
new Handle:g_hHard_enable;
new Handle:g_hHard_enable_sur;
new Handle:g_hHard_enable_vs;
new Handle:g_hHard_hpmult;
//associated var
new g_iHard_enable;
new g_iHard_enable_sur;
new g_iHard_enable_vs;
new Float:g_flHard_hpmult;

//martial artist, movement rate     近战专家
//campaign, non-campaign
new Handle:g_hMA_enable;
new Handle:g_hMA_enable_sur;
new Handle:g_hMA_enable_vs;
new Handle:g_hMA_maxpenalty;
//associated var
new g_iMA_enable;
new g_iMA_enable_sur;
new g_iMA_enable_vs;
new g_iMA_maxpenalty;

//extreme conditioning, movement rate       凌波微步
//campaign, non-campaign
new Handle:g_hExtreme_enable;
new Handle:g_hExtreme_enable_sur;
new Handle:g_hExtreme_enable_vs;
new Handle:g_hExtreme_rate;
//associated var
new g_iExtreme_enable;
new g_iExtreme_enable_sur;
new g_iExtreme_enable_vs;
new Float:g_flExtreme_rate;

//little leaguer                            棒球队员
new Handle:g_hLittle_enable;
new Handle:g_hLittle_enable_sur;
new Handle:g_hLittle_enable_vs;
//associated var
new g_iLittle_enable;
new g_iLittle_enable_sur;
new g_iLittle_enable_vs;


//INF1 (BOOMER) PERKS           特感技能    BOOMER
//blind luck, cooldown multiplier           遗忘之咒
//one-size-fits-all
new Handle:g_hBlind_enable;
new Handle:g_hBlind_cdmult;
//associated var
new g_iBlind_enable;
new Float:g_flBlind_cdmult;

//dead wreckening, damage multiplier        致命袭击
//one-size-fits-all
new Handle:g_hDead_enable;
new Handle:g_hDead_dmgmult;
//associated var
new g_iDead_enable;
new Float:g_flDead_dmgmult;

//barf bagged                                   恶魔唤醒
new Handle:g_hBarf_enable;
new g_iBarf_enable;

//motion sickness                               身体轻盈
//one-size-fits-all
new Handle:g_hMotion_rate;
new Handle:g_hMotion_enable;
//associated vars
new Float:g_flMotion_rate;
new g_iMotion_enable;


//INF3 (SMOKER) PERKS       特感  SMOKER
//tongue twister, multipliers for tongue speed, pull speed, range
//one-size-fits-all                    夺命舌速
new Handle:g_hTongue_enable;
new Handle:g_hTongue_speedmult;
new Handle:g_hTongue_pullmult;
new Handle:g_hTongue_rangemult;
//associated vars
new g_iTongue_enable;
new Float:g_flTongue_speedmult;
new Float:g_flTongue_pullmult;
new Float:g_flTongue_rangemult;

//squeezer, bonus damage            致命之怀
//normal, hard, expert
//*used by bots in all modes
new Handle:g_hSqueezer_enable;
new Handle:g_hSqueezer_dmgmult;
//associated var
new g_iSqueezer_enable;
new Float:g_flSqueezer_dmgmult;

//drag and drop, cooldown mult;         控舌
//one-size-fits-all
new Handle:g_hDrag_enable;
new Handle:g_hDrag_cdmult;
//associated var
new g_iDrag_enable;
new Float:g_flDrag_cdmult;

//smoke it                                      专业拖车
new Handle:g_hSmokeItSpeed;
new Handle:g_hSmokeItTimer[MAXPLAYERS+1];
new Handle:g_hSmokeItMaxRange;
new Handle:g_hSmokeIt_enable;
//associated vars
new Float:g_flSmokeItSpeed;
new bool:g_bSmokeItGrabbed[MAXPLAYERS+1];
new g_iSmokeItMaxRange;
new g_iSmokeIt_enable;



//INF4 (HUNTER) PERKS           HUNTER
//body slam, minbound           鬼影之锤
//one-size-fits-all
new Handle:g_hBody_enable;
new Handle:g_hBody_minbound;
//associated var
new g_iBody_enable;
new g_iBody_minbound;

//efficient killer, bonus damage        夺命杀手
//normal, hard, expert
//*used by bots in all modes
new Handle:g_hEfficient_enable;
new Handle:g_hEfficient_dmgmult;
//associated var
new g_iEfficient_enable;
new Float:g_flEfficient_dmgmult;

//grasshopper, speed multiplier         黑暗之忍
//one-size-fits-all
new Handle:g_hGrass_enable;
new Handle:g_hGrass_rate;
//associated var
new g_iGrass_enable;
new Float:g_flGrass_rate;

//speed demon, speed multiplier         夺命印记
//one-size-fits-all
new Handle:g_hSpeedDemon_enable;
new Handle:g_hSpeedDemon_rate;
new Handle:g_hSpeedDemon_dmgmult;
//associated var
new g_iSpeedDemon_enable;
new Float:g_flSpeedDemon_rate;
new Float:g_flSpeedDemon_dmgmult;


//INF2 (TANK) PERKS                                                 TANK
//adrenal glands, multipliers for punch cooldown,       疯狂暴走
//throw rock cooldown, and rock travel speed
//one-size-fits-all
new Handle:g_hAdrenal_enable;
new Handle:g_hAdrenal_punchcdmult;
new Handle:g_hAdrenal_throwcdmult;
//associated vars
new g_iAdrenal_enable;
new Float:g_flAdrenal_punchcdmult;
new Float:g_flAdrenal_throwcdmult;

//juggernaut, bonus health                              魔王重生
//one-size-fits-all
new Handle:g_hJuggernaut_enable;
new Handle:g_hJuggernaut_hp;
//associated var
new g_iJuggernaut_enable;
new g_iJuggernaut_hp;

//metabolic boost, speed multiplier                 神行百变
//one-size-fits-all
new Handle:g_hMetabolic_enable;
new Handle:g_hMetabolic_speedmult;
//associated var
new g_iMetabolic_enable;
new Float:g_flMetabolic_speedmult;

//storm caller, mobs spawned                        王者召唤
//one-size-fits-all
new Handle:g_hStorm_enable;
new Handle:g_hStorm_mobcount;
//associated var
new g_iStorm_enable;
new g_iStorm_mobcount;

//double the trouble, health multiplier             分身
//one-size-fits-all
new Handle:g_hDouble_enable;
new Handle:g_hDouble_hpmult;
//associated var
new g_iDouble_enable;
new Float:g_flDouble_hpmult;


//INF5 (JOCKEY) PERKS                                JOCKEY
//ride like the wind, runspeed multiplier           专业车手
//one-size-fits-all
new Handle:g_hWind_enable;
new Handle:g_hWind_rate;
//associated var
new g_iWind_enable;
new Float:g_flWind_rate;

//cavalier, hp multiplier                           血气旺盛
new Handle:g_hCavalier_enable;
new Handle:g_hCavalier_hpmult;
//associated vars
new g_iCavalier_enable;
new Float:g_flCavalier_hpmult;

//frogger, dmg multiplier, leap multiplier          嗜血之鞍
new Handle:g_hFrogger_enable;
new Handle:g_hFrogger_dmgmult;
new Handle:g_hFrogger_rate;
//associated vars
new g_iFrogger_enable;
new Float:g_flFrogger_dmgmult;
new Float:g_flFrogger_rate;

//ghost rider, invis amount              幽灵幻术
new Handle:g_hGhost_enable;
new Handle:g_hGhost_alpha;
//associated vars
new g_iGhost_enable;
new g_iGhost_alpha;


//INF6 (SPITTER) PERKS                  SPITTER
//twin spitfire, time delay between two shots           多重喷射
//one-size-fits-all
new Handle:g_hTwinSF_enable;
new Handle:g_hTwinSF_delay;
//associated var
new g_iTwinSF_enable;
new Float:g_flTwinSF_delay;

//mega adhesive, slow multiplier                粘性毒液
//one-size-fits-all
new Handle:g_hMegaAd_enable;
new Handle:g_hMegaAd_slow;
//associated var
new g_iMegaAd_enable;
new Float:g_flMegaAd_slow;

//lethal dose, min damage                       致命毒液
/*
new Handle:g_hLethal_enable;
new Handle:g_hLethal_minbound;
//associated var
new g_iLethal_enable;
new g_iLethal_minbound;
*/


//INF7 (CHARGER) PERKS                      CHARGER
//scattering ram, charge force multiplier and maximum cooldown
//one-size-fits-all                                     战神之力
new Handle:g_hScatter_enable;
new Handle:g_hScatter_force;
new Handle:g_hScatter_hpmult;
//associated var
new g_iScatter_enable;
new Float:g_flScatter_force;
new Float:g_flScatter_hpmult;

//speeding bullet, charge moverate              急速冲击
new Handle:g_hBullet_enable;
new Handle:g_hBullet_rate;
//associated vars
new g_iBullet_enable;
new Float:g_flBullet_rate;
/*
//unstoppable force, hpmult and runspeed mult
new Handle:g_hUnstop_enable;
new Handle:g_hUnstop_hpmult;
new Handle:g_hUnstop_rate;
//associated vars
new g_iUnstop_enable;
new Float:g_flUnstop_hpmult;
new Float:g_flUnstop_rate;
*/



//BOT CONTROLLER VARS                   机器人技能
//these track the server's preference
//for what perks bots should use

//survivor
new Handle:g_hBot_Sur1;
new Handle:g_hBot_Sur2;
new Handle:g_hBot_Sur3;
//boomer
new Handle:g_hBot_Inf1;
//smoker
new Handle:g_hBot_Inf3;
//hunter
new Handle:g_hBot_Inf4;
//tank
new Handle:g_hBot_Inf2;
new Handle:g_hBot_Inf5;
new Handle:g_hBot_Inf6;
new Handle:g_hBot_Inf7;

//DEFAULT PERKS                         默认技能
//These vars track the server's
//given default perks, to account
//for disabling perks

//sur1
new Handle:g_hSur1_default;
new g_iSur1_default;

//sur2
new Handle:g_hSur2_default;
new g_iSur2_default;

//sur3
new Handle:g_hSur3_default;
new g_iSur3_default;

//inf1/boomer
new Handle:g_hInf1_default;
new g_iInf1_default;

//inf3/smoker
new Handle:g_hInf3_default;
new g_iInf3_default;

//inf4/hunter
new Handle:g_hInf4_default;
new g_iInf4_default;

//inf2/tank
new Handle:g_hInf2_default;
new g_iInf2_default;

//inf5/jockey
new Handle:g_hInf5_default;
new g_iInf5_default;

//inf6/spitter
new Handle:g_hInf6_default;
new g_iInf6_default;

//inf7/charger
new Handle:g_hInf7_default;
new g_iInf7_default;

//FORCE RANDOM PERKS            随机技能
//tracks server setting for
//whether to force random perks

new Handle:g_hForceRandom;
new g_iForceRandom;

//ENABLE RANDOM PERKS BY PLAYER CHOICE      玩家可选择随机
//tracks whether player can
//randomize their perks

new Handle:g_hRandomEnable;
new g_iRandomEnable;

//PERK TREES AVAILABILITY       各个技能列表是否可选
//option for servers to completely
//disable entire perk trees

new Handle:g_hSur1_enable;
new Handle:g_hSur2_enable;
new Handle:g_hSur3_enable;
new Handle:g_hInf1_enable;
new Handle:g_hInf2_enable;
new Handle:g_hInf3_enable;
new Handle:g_hInf4_enable;
new Handle:g_hInf5_enable;
new Handle:g_hInf6_enable;
new Handle:g_hInf7_enable;
new g_iSur1_enable;
new g_iSur2_enable;
new g_iSur3_enable;
new g_iInf1_enable;
new g_iInf2_enable;
new g_iInf3_enable;
new g_iInf4_enable;
new g_iInf5_enable;
new g_iInf6_enable;
new g_iInf7_enable;

//this var keeps track of whether
//to enable DT and Stopping or not, so we don't
//have to do the checks every game frame, or
//every time someone gets hurt

new g_iDT_meta_enable = 1;
new g_iStopping_meta_enable = 1;
new g_iMA_meta_enable = 1;

//controls whether menu automatically shows 控制菜单是否自动出现
new Handle:g_hMenuAutoShow_enable;

new g_spitter_ability_count[MAXPLAYERS+1];        // 口水重置次数计时

//=============================
// Hooking, Initialize Vars  各个参数初始化
//=============================

public OnPluginStart()
{
    //Plugin version for online tracking
    CreateConVar("l4d_perkmod_version", PLUGIN_VERSION, "Version of Perkmod2 for L4D2", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY);

    decl String:stGame[32];
    GetGameFolderName(stGame, 32);
    if (StrEqual(stGame, "left4dead2", false)==true)
    {
        g_iL4D_12 = 2;
        LogMessage("L4D 2 detected.");
    }
    else if (StrEqual(stGame, "left4dead", false)==true)
    {
        g_iL4D_12 = 1;
        LogMessage("L4D 1 detected.");
    }
    else
        SetFailState("Perkmod only supports L4D 1 or 2.");
    
    //PERK FUNCTIONS  插件初始化
    //anything here that pertains to the actual
    //workings of the perks (ie, events and timers)

    //hooks for Sur1 perks
    HookEvent("player_hurt", Event_PlayerHurtPre, EventHookMode_Pre);       // 幸存伤害
    HookEvent("infected_hurt", Event_InfectedHurtPre, EventHookMode_Pre);   // 感染者伤害
    HookEvent("item_pickup", Event_ItemPickup);             // 捡物品
    HookEvent("spawner_give_item", Event_ItemPickup);   // 发送物品
    HookEvent("weapon_fire", Event_WeaponFire);     // 开枪
    HookEvent("lunge_pounce", Event_PounceLanded);  //  hunter撕抓
    HookEvent("pounce_stopped", Event_PounceStop); // hunter撕抓停止
    HookEvent("player_ledge_grab", Event_LedgeGrab); // 幸存挂边
    HookEvent("player_team", Event_PlayerTeam); // 队伍调整
    HookEvent("weapon_reload", Event_Reload); // 武器技能重载
    HookEvent("heal_success", Event_PlayerHealed); // 治疗事件
    HookEvent("survivor_rescued", Event_PlayerRescued);// 营救幸存
    HookEvent("pills_used", Event_PillsUsed, EventHookMode_Pre);// 使用药丸
    HookEvent("revive_begin", Event_ReviveBeginPre, EventHookMode_Pre); // 营救开始
    HookEvent("revive_success", Event_ReviveSuccess);// 救人事件
    HookEvent("ammo_pickup", Event_AmmoPickup);//  捡子弹
    HookEvent("player_incapacitated", Event_Incap);// 玩家被控
    HookEvent("player_now_it", Event_PlayerNowIt);  // 胖子喷中
    HookEvent("ability_use", Event_AbilityUsePre, EventHookMode_Pre);
    HookEvent("tongue_grab", Event_TongueGrabPre, EventHookMode_Pre);// 舌头缠住
    HookEvent("tongue_release", Event_TongueRelease);// 舌头释放
    HookEvent("choke_end", Event_TongueRelease);
    HookEvent("tongue_broke_bent", Event_TongueRelease_novictimid);
    HookEvent("choke_stopped", Event_TongueRelease_newsmokerid);
    HookEvent("tongue_pull_stopped", Event_TongueRelease_newsmokerid);
    HookEvent("player_spawn", Event_PlayerSpawn);// 玩家创建，技能初始化
    HookEvent("ability_use", Event_AbilityUse);
    HookEvent("tank_spawn", Event_Tank_Spawn); // 创建tank
    HookEvent("tank_frustrated", Event_Tank_Frustrated, EventHookMode_Pre); // tank掉控制事件处理
    HookEvent("player_first_spawn", Event_PlayerFirstSpawn);// 玩家菜单显示，初始化
    HookEvent("player_transitioned", Event_PlayerTransitioned);//玩家角色或队伍调整，重置技能菜单
    HookEvent("player_connect_full", Event_PConnect);// 玩家链接服务器
    HookEvent("player_disconnect", Event_PDisconnect);// 玩家断开服务器
    HookEvent("round_start", Event_RoundStart);// 回合轮换开始
    HookEvent("player_death", Event_PlayerDeath);// 玩家死亡
    HookEvent("round_end", Event_RoundEnd);// 轮换结束
	
    //RegConsoleCmd("say", MenuOpen_OnSay);
    //RegConsoleCmd("say_team", MenuOpen_OnSay);
    RegConsoleCmd("sm_s", MenuOpen_OnSay);// 菜单显示
    HookConVarChange(FindConVar("mp_gamemode"),Convar_GameMode);// 游戏模式，合作，生存，对抗  

    //l4d2 only hooks
    if (g_iL4D_12 == 2)
    {
        HookEvent("jockey_ride", Event_JockeyRide); // 猴子骑行开始
        HookEvent("jockey_ride_end", Event_JockeyRideEnd);// 猴子骑行结束
        HookEvent("charger_pummel_start", Event_ChargerPummelStart);// 牛攻击（右拳）
        HookEvent("charger_pummel_end", Event_ChargerPummelEnd);// 牛攻击（右拳）
        HookEvent("charger_impact", Event_ChargerImpact); // 牛顶撞开始
        HookEvent("charger_charge_end", Event_ChargerChargeEnd);// 牛顶撞结束
        HookEvent("charger_carry_end", Event_ChargerChargeEnd);
        HookEvent("adrenaline_used", Event_PillsUsed, EventHookMode_Pre);// 药丸使用
        HookEvent("player_jump", Event_Jump);// 跳跃事件
    }

    //debug
    //RegConsoleCmd("say", Debug_OnSay);
    //RegConsoleCmd("say_team", Debug_OnSay);

    //init vars 初始化参数
    g_flVomitFatigue    =    GetConVarFloat(FindConVar("z_vomit_fatigue")); // 胖子喷吐
    g_flTongueSpeed        =    GetConVarFloat(FindConVar("tongue_victim_max_speed")); // 舌头最大速度
    g_flTongueFlySpeed    =    GetConVarFloat(FindConVar("tongue_fly_speed")); // 舌头飞行速度
    g_flTongueRange        =    GetConVarFloat(FindConVar("tongue_range")); // 舌头范围
    g_flTongueDropTime    =    GetConVarFloat(FindConVar("tongue_player_dropping_to_ground_time")); // 控舌CD
    

    //get offsets 获取offset值
    g_iHPBuffO            =    FindSendPropOffs("CTerrorPlayer","m_healthBuffer");
    g_iHPBuffTimeO        =    FindSendPropOffs("CTerrorPlayer","m_healthBufferTime");
    g_iRevCountO        =    FindSendPropOffs("CTerrorPlayer","m_currentReviveCount");
    g_iMeleeFatigueO    =    FindSendPropInfo("CTerrorPlayer","m_iShovePenalty");
    g_iNextPAttO        =    FindSendPropInfo("CBaseCombatWeapon","m_flNextPrimaryAttack");
    g_iNextSAttO        =    FindSendPropInfo("CBaseCombatWeapon","m_flNextSecondaryAttack");
    g_iActiveWO            =    FindSendPropInfo("CBaseCombatCharacter","m_hActiveWeapon");
    g_iShotStartDurO    =    FindSendPropInfo("CBaseShotgun","m_reloadStartDuration");
    g_iShotInsertDurO    =    FindSendPropInfo("CBaseShotgun","m_reloadInsertDuration");
    g_iShotEndDurO        =    FindSendPropInfo("CBaseShotgun","m_reloadEndDuration");
    g_iPlayRateO        =    FindSendPropInfo("CBaseCombatWeapon","m_flPlaybackRate");
    g_iShotRelStateO    =    FindSendPropInfo("CBaseShotgun","m_reloadState");
    g_iNextAttO            =    FindSendPropInfo("CTerrorPlayer","m_flNextAttack");
    g_iTimeIdleO        =    FindSendPropInfo("CTerrorGun","m_flTimeWeaponIdle");
    g_iLaggedMovementO    =    FindSendPropInfo("CTerrorPlayer","m_flLaggedMovementValue");
    g_iFrustrationO        =    FindSendPropInfo("Tank","m_frustration");
    g_iAbilityO            =    FindSendPropInfo("CTerrorPlayer","m_customAbility");
    g_iClassO            =    FindSendPropInfo("CTerrorPlayer","m_zombieClass");
    g_iVMStartTimeO        =    FindSendPropInfo("CTerrorViewModel","m_flLayerStartTime");
    g_iViewModelO        =    FindSendPropInfo("CTerrorPlayer","m_hViewModel");
    g_iIncapO            =    FindSendPropInfo("CTerrorPlayer","m_isIncapacitated");
    g_iIsGhostO            =    FindSendPropInfo("CTerrorPlayer","m_isGhost");
    g_iClipO            =    FindSendPropInfo("CTerrorGun","m_iClip1");

    //CREATE AND INITIALIZE CONVARS
    //everything related to the convars that adjust
    //certain values for the perks
    CreateConvars();

    //finally, run a command to exec the .cfg file
    //to load the server's preferences for these cvars
    //AutoExecConfig(true , "JiNeng");

    //and load translations
    LoadTranslations("plugin.perkmod");
}

//just to give me a bit less of a headache, 插件运行参数设置
//all convar creation is called here
CreateConvars()
{
    //SURVIVOR
    //stopping power 枪神技能参数
    g_hStopping_dmgmult = CreateConVar(// 枪神伤害增幅
        "l4d_perkmod_stoppingpower_damagemultiplier" ,
        "1.76" ,
        "枪神附身技能的攻击力 (设定值范围 0.05 < 1.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hStopping_dmgmult, Convar_Stopping);
    g_flStopping_dmgmult = 1.76;

    g_hStopping_enable = CreateConVar( // 是否允许在合作模式使用技能
        "l4d_perkmod_stoppingpower_enable" ,
        "1" ,
        "枪神附身技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hStopping_enable, Convar_Stopping_en);
    g_iStopping_enable = 1;

    g_hStopping_enable_sur = CreateConVar(// 是否允许在生存模式使用技能
        "l4d_perkmod_stoppingpower_enable_survival" ,
        "1" ,
        "枪神附身技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hStopping_enable_sur, Convar_Stopping_en_sur);
    g_iStopping_enable_sur = 1;

    g_hStopping_enable_vs = CreateConVar(// 是否允许在对抗模式使用技能
        "l4d_perkmod_stoppingpower_enable_versus" ,
        "1" ,
        "枪神附身技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hStopping_enable_vs, Convar_Stopping_en_vs);
    g_iStopping_enable_vs = 1;

    //pyrotechnician 爆破专家技能参数
    g_hPyro_enable = CreateConVar(// 是否允许在合作模式使用技能
        "l4d_perkmod_pyrotechnician_enable" ,
        "1" ,
        "爆破专家技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hPyro_enable, Convar_Pyro_en);
    g_iPyro_enable = 1;

    g_hPyro_enable_sur = CreateConVar(// 是否允许在生存模式使用技能
        "l4d_perkmod_pyrotechnician_enable_survival" ,
        "1" ,
        "爆破专家技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hPyro_enable_sur, Convar_Pyro_en_sur);
    g_iPyro_enable_sur = 1;

    g_hPyro_enable_vs = CreateConVar(// 是否允许在对抗模式使用技能
        "l4d_perkmod_pyrotechnician_enable_versus" ,
        "1" ,
        "爆破专家技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hPyro_enable_vs, Convar_Pyro_en_vs);
    g_iPyro_enable_vs = 1;

    //spirit        黄金战甲技能参数
    g_hSpirit_buff = CreateConVar(  // 自起额外血量
        "l4d_perkmod_spirit_bonusbuffer" ,
        "400" ,
        "黄金战甲技能倒地后站起拥有的血量 (设定值范围 0 < 170)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpirit_buff, Convar_SpiritBuff);
    g_iSpirit_buff=                400;

    g_hSpirit_cd = CreateConVar(// 合作模式自起CD
        "l4d_perkmod_spirit_cooldown" ,
        "7" ,
        "黄金战甲技能在战役模式中再次使用的间隔时间 (设定值范围 1秒 < 1800秒)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpirit_cd, Convar_SpiritCD);
    g_iSpirit_cd=                7;

    g_hSpirit_cd_sur = CreateConVar(// 幸存模式自起CD
        "l4d_perkmod_spirit_cooldown_sur" ,
        "7" ,
        "黄金战甲技能在生存模式中再次使用的间隔时间 (设定值范围 1秒 < 1800秒)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpirit_cd_sur, Convar_SpiritCDsur);
    g_iSpirit_cd_sur=            7;

    g_hSpirit_cd_vs = CreateConVar(// 对抗模式自起CD
        "l4d_perkmod_spirit_cooldown_vs" ,
        "7" ,
        "黄金战甲技能在对抗模式中再次使用的间隔时间 (设定值范围 1秒 < 1800秒)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpirit_cd_vs, Convar_SpiritCDvs);
    g_iSpirit_cd_vs=            7;

    g_hSpirit_enable = CreateConVar(// 是否允许在合作模式使用技能
        "l4d_perkmod_spirit_enable" ,
        "1" ,
        "黄金战甲技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpirit_enable, Convar_Spirit_en);
    g_iSpirit_enable = 1;

    g_hSpirit_enable_sur = CreateConVar(// 是否允许在生存模式使用技能
        "l4d_perkmod_spirit_enable_survival" ,
        "1" ,
        "黄金战甲技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpirit_enable_sur, Convar_Spirit_en_sur);
    g_iSpirit_enable_sur = 1;

    g_hSpirit_enable_vs = CreateConVar(// 是否允许在对抗模式使用技能
        "l4d_perkmod_spirit_enable_versus" ,
        "1" ,
        "黄金战甲技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpirit_enable_vs, Convar_Spirit_en_vs);
    g_iSpirit_enable_vs = 1;

    //double tap  夺命射速技能参数
    g_hDT_rate = CreateConVar( // 射速速率
        "l4d_perkmod_doubletap_rate" ,
        "0.35" ,
        "夺命速射技能的速度(设定值范围 0.2 < 0.9)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDT_rate, Convar_DT);
    g_flDT_rate=            0.35;

    g_hDT_freechance = CreateConVar(// 子弹打出间隔CD
        "l4d_perkmod_doubletap_freechance" ,
        "0.0" ,
        "夺命速射某次射击不损耗子弹的几率 (设定值范围0.0 < 0.5)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDT_freechance, Convar_DT_freechance);
    g_flDT_freechance=            0.0;

    g_hDT_enable = CreateConVar(// 是否允许在合作模式使用技能
        "l4d_perkmod_doubletap_enable" ,
        "1" ,
        "夺命速射技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDT_enable, Convar_DT_en);
    g_iDT_enable = 1;

    g_hDT_enable_sur = CreateConVar(// 是否允许在生存模式使用技能
        "l4d_perkmod_doubletap_enable_survival" ,
        "1" ,
        "夺命速射技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDT_enable_sur, Convar_DT_en_sur);
    g_iDT_enable_sur = 1;

    g_hDT_enable_vs = CreateConVar(// 是否允许在对抗模式使用技能
        "l4d_perkmod_doubletap_enable_versus" ,
        "1" ,
        "夺命速射技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDT_enable_vs, Convar_DT_en_vs);
    g_iDT_enable_vs = 1;

    //sleight of hand  急速上弹技能参数
    g_hSoH_rate = CreateConVar(
        "l4d_perkmod_sleightofhand_rate" ,
        "0.11" ,
        "极速上弹技能的速度 (设定值范围 0.2 < 0.9)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSoH_rate, Convar_SoH);
    g_flSoH_rate=            0.11;

    g_hSoH_enable = CreateConVar(
        "l4d_perkmod_sleightofhand_enable" ,
        "1" ,
        "极速上弹技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSoH_enable, Convar_SoH_en);
    g_iSoH_enable = 1;

    g_hSoH_enable_sur = CreateConVar(
        "l4d_perkmod_sleightofhand_enable_survival" ,
        "1" ,
        "极速上弹技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSoH_enable_sur, Convar_SoH_en_sur);
    g_iSoH_enable_sur = 1;

    g_hSoH_enable_vs = CreateConVar(
        "l4d_perkmod_sleightofhand_enable_versus" ,
        "1" ,
        "极速上弹技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSoH_enable_vs, Convar_SoH_en_vs);
    g_iSoH_enable_vs = 1;

    //unbreakable  医疗专家技能参数
    g_hUnbreak_hp = CreateConVar(
        "l4d_perkmod_unbreakable_bonushealth" ,
        "390" ,
        "医疗专家技能补充的血量 (设定值范围 1 < 100)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hUnbreak_hp, Convar_Unbreak);
    g_iUnbreak_hp = 390;

    g_hUnbreak_enable = CreateConVar(
        "l4d_perkmod_unbreakable_enable" ,
        "1" ,
        "医疗专家技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hUnbreak_enable, Convar_Unbreak_en);
    g_iUnbreak_enable = 1;

    g_hUnbreak_enable_sur = CreateConVar(
        "l4d_perkmod_unbreakable_enable_survival" ,
        "1" ,
        "医疗专家技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hUnbreak_enable_sur, Convar_Unbreak_en_sur);
    g_iUnbreak_enable_sur = 1;

    g_hUnbreak_enable_vs = CreateConVar(
        "l4d_perkmod_unbreakable_enable_versus" ,
        "1" ,
        "医疗专家技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hUnbreak_enable_vs, Convar_Unbreak_en_vs);
    g_iUnbreak_enable_vs = 1;

    //chem reliant   枪支专家技能参数
    g_hChem_buff = CreateConVar(
        "l4d_perkmod_chemreliant_bonusbuffer" ,
        "100" ,
        "枪支专家技能可以补充的血量 (设定值范围 1 < 150)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hChem_buff, Convar_Chem);
    g_iChem_buff = 100;

    g_hChem_enable = CreateConVar(
        "l4d_perkmod_chemreliant_enable" ,
        "1" ,
        "枪支专家技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hChem_enable, Convar_Chem_en);
    g_iChem_enable = 1;

    g_hChem_enable_sur = CreateConVar(
        "l4d_perkmod_chemreliant_enable_survival" ,
        "1" ,
        "枪支专家技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hChem_enable_sur, Convar_Chem_en_sur);
    g_iChem_enable_sur = 1;

    g_hChem_enable_vs = CreateConVar(
        "l4d_perkmod_chemreliant_enable_versus" ,
        "1" ,
        "枪支专家技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hChem_enable_vs, Convar_Chem_en_vs);
    g_iChem_enable_vs = 1;

    //helping hand 救护专家技能参数
    g_hHelpHand_timemult = CreateConVar(
        "l4d_perkmod_helpinghand_timemultiplier" ,
        "0.1" ,
        "救护医师技能的救人速度 (设定值范围 0.01 < 1.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHelpHand_timemult, Convar_HelpTime);
    g_flHelpHand_timemult = 0.7;
    g_flReviveTime        = 1.1; // 幸存拉起来CD

    g_hHelpHand_buff = CreateConVar(
        "l4d_perkmod_helpinghand_bonusbuffer" ,
        "160" ,
        "救护医师在生存/战役模式中救起队友后给予队友临时的血量(设定值范围 0 < 170)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHelpHand_buff, Convar_HelpBuff);
    g_iHelpHand_buff = 200;

    g_hHelpHand_buff_vs = CreateConVar(
        "l4d_perkmod_helpinghand_bonusbuffer_vs" ,
        "180" ,
        "救护医师在对抗模式中救起队友后给予队友临时的血量(设定值范围 0 < 170)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHelpHand_buff_vs, Convar_HelpBuffvs);
    g_iHelpHand_buff_vs = 200;

    g_hHelpHand_enable = CreateConVar(
        "l4d_perkmod_helpinghand_enable" ,
        "1" ,
        "救护医师技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHelpHand_enable, Convar_Help_en);
    g_iHelpHand_enable = 1;

    g_hHelpHand_enable_sur = CreateConVar(
        "l4d_perkmod_helpinghand_enable_survival" ,
        "1" ,
        "救护医师技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHelpHand_enable_sur, Convar_Help_en_sur);
    g_iHelpHand_enable_sur = 1;

    g_hHelpHand_enable_vs = CreateConVar(
        "l4d_perkmod_helpinghand_enable_versus" ,
        "1" ,
        "救护医师技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHelpHand_enable_vs, Convar_Help_en_vs);
    g_iHelpHand_enable_vs = 1;

    g_hHelpHand_convar = CreateConVar(
        "l4d_perkmod_helpinghand_enable_convarchanges" ,
        "0" ,
        "救护医师技能是否启用调整服务器参数的设定, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHelpHand_convar, Convar_Help_convar);
    g_iHelpHand_convar = 1;

    //pack rat  弹药专家技能参数
    g_hPack_ammomult = CreateConVar(
        "l4d_perkmod_packrat_ammomultiplier" ,
        "7.7" ,
        "弹药专家技能增加的子弹数比率 (设定值范围 0.01 < 1.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hPack_ammomult, Convar_Pack);
    g_flPack_ammomult = 7.7;

    g_hPack_enable = CreateConVar(
        "l4d_perkmod_packrat_enable" ,
        "1" ,
        "弹药专家技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hPack_enable, Convar_Pack_en);
    g_iPack_enable = 1;

    g_hPack_enable_sur = CreateConVar(
        "l4d_perkmod_packrat_enable_survival" ,
        "1" ,
        "弹药专家技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hPack_enable_sur, Convar_Pack_en_sur);
    g_iPack_enable_sur = 1;

    g_hPack_enable_vs = CreateConVar(
        "l4d_perkmod_packrat_enable_versus" ,
        "1" ,
        "弹药专家技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hPack_enable_vs, Convar_Pack_en_vs);
    g_iPack_enable_vs = 1;

    //hard to kill  坚韧之躯技能参数
    g_hHard_hpmult = CreateConVar(
        "l4d_perkmod_hardtokill_healthmultiplier" ,
        "1.75" ,
        "坚韧之躯技能在幸存者倒地后增加防御血量的比率 (设定值范围 0.01 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHard_hpmult, Convar_Hard);
    g_flHard_hpmult = 1.75;

    g_hHard_enable = CreateConVar(
        "l4d_perkmod_hardtokill_enable" ,
        "1" ,
        "坚韧之躯技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHard_enable, Convar_Hard_en);
    g_iHard_enable = 1;

    g_hHard_enable_sur = CreateConVar(
        "l4d_perkmod_hardtokill_enable_survival" ,
        "1" ,
        "坚韧之躯技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHard_enable_sur, Convar_Hard_en_sur);
    g_iHard_enable_sur = 1;

    g_hHard_enable_vs = CreateConVar(
        "l4d_perkmod_hardtokill_enable_versus" ,
        "1" ,
        "坚韧之躯技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hHard_enable_vs, Convar_Hard_en_vs);
    g_iHard_enable_vs = 1;

    //martial artist  近战专家技能参数
    g_hMA_maxpenalty = CreateConVar(
        "l4d_perkmod_martialartist_maximumpenalty" ,
        "1" ,
        "近战专家技能的最大攻击力(设定值范围 1 < 6)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMA_maxpenalty, Convar_MA_maxpenalty);
    g_iMA_maxpenalty = 1;

    g_hMA_enable = CreateConVar(
        "l4d_perkmod_martialartist_enable" ,
        "1" ,
        "近战专家技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMA_enable, Convar_MA_en);
    g_iMA_enable = 1;

    g_hMA_enable_sur = CreateConVar(
        "l4d_perkmod_martialartist_enable_survival" ,
        "1" ,
        "近战专家技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMA_enable_sur, Convar_MA_en_sur);
    g_iMA_enable_sur = 1;

    g_hMA_enable_vs = CreateConVar(
        "l4d_perkmod_martialartist_enable_versus" ,
        "1" ,
        "近战专家技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMA_enable_vs, Convar_MA_en_vs);
    g_iMA_enable_vs = 1;

    //extreme conditioning  凌波微步技能参数
    g_hExtreme_rate = CreateConVar(
        "l4d_perkmod_extremeconditioning_rate" ,
        "1.30" ,
        "凌波微步技能的速度比率 (设定值范围 1.0 < 1.5)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hExtreme_rate, Convar_Extreme);
    g_flExtreme_rate = 1.30;

    g_hExtreme_enable = CreateConVar(
        "l4d_perkmod_extremeconditioning_enable" ,
        "1" ,
        "凌波微步技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hExtreme_enable, Convar_Extreme_en);
    g_iExtreme_enable = 1;

    g_hExtreme_enable_sur = CreateConVar(
        "l4d_perkmod_extremeconditioning_enable_survival" ,
        "1" ,
        "凌波微步技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hExtreme_enable_sur, Convar_Extreme_en_sur);
    g_iExtreme_enable_sur = 1;

    g_hExtreme_enable_vs = CreateConVar(
        "l4d_perkmod_extremeconditioning_enable_versus" ,
        "1" ,
        "凌波微步技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hExtreme_enable_vs, Convar_Extreme_en_vs);
    g_iExtreme_enable_vs = 1;

    //little leaguer    棒球队员技能参数
    g_hLittle_enable = CreateConVar(
        "l4d_perkmod_littleleaguer_enable" ,
        "1" ,
        "棒球队员技能是否可以在战役模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hLittle_enable, Convar_Little_en);
    g_iLittle_enable = 1;

    g_hLittle_enable_sur = CreateConVar(
        "l4d_perkmod_littleleaguer_enable_survival" ,
        "1" ,
        "棒球队员技能是否可以在生存模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hLittle_enable_sur, Convar_Little_en_sur);
    g_iLittle_enable_sur = 1;

    g_hLittle_enable_vs = CreateConVar(
        "l4d_perkmod_littleleaguer_enable_versus" ,
        "1" ,
        "棒球队员技能是否可以在对抗模式中使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hLittle_enable_vs, Convar_Little_en_vs);
    g_iLittle_enable_vs = 1;



    //BOOMER   胖子技能参数
    //barf bagged
    g_hBarf_enable = CreateConVar(
        "l4d_perkmod_barfbagged_enable" ,
        "1" ,
        "BOOMER恶魔唤醒技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hBarf_enable, Convar_Barf_en);
    g_iBarf_enable = 1;

    //blind luck
    g_hBlind_cdmult = CreateConVar(
        "l4d_perkmod_blindluck_timemultiplier" ,
        "0" ,
        "BOOMER遗忘之咒缩短技能恢复时间调整(默认30秒) (设定值范围 0.01 < 1.0 , 设定值说明:0.5=30秒 , 1=60秒)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hBlind_cdmult, Convar_Blind);
    g_flBlind_cdmult = 0.1;

    g_hBlind_enable = CreateConVar(
        "l4d_perkmod_blindluck_enable" ,
        "1" ,
        "BOOMER遗忘之咒技能是否可以使用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hBlind_enable, Convar_Blind_en);
    g_iBlind_enable = 1;

    //dead wreckening
    g_hDead_dmgmult = CreateConVar(
        "l4d_perkmod_deadwreckening_damagemultiplier" ,
        "21.5" ,
        "BOOMER致命袭击小丧尸对人类的伤害值" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDead_dmgmult, Convar_Dead);
    g_flDead_dmgmult = 21.5;

    g_hDead_enable = CreateConVar(
        "l4d_perkmod_deadwreckening_enable" ,
        "1" ,
        "BOOMER致命袭击技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDead_enable, Convar_Dead_en);
    g_iDead_enable = 1;

    //motion sickness
    g_hMotion_rate = CreateConVar(
        "l4d_perkmod_motionsickness_rate" ,
        "4.70" ,
        "BOOMER身体轻盈技能的移动速度 (设定值范围 1.0 < 4.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMotion_rate, Convar_Motion);
    g_flMotion_rate = 4.70;

    g_hMotion_enable = CreateConVar(
        "l4d_perkmod_motionsickness_enable" ,
        "1" ,
        "BOOMER身体轻盈技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMotion_enable, Convar_Motion_en);
    g_iMotion_enable = 1;



    //SMOKER 舌头技能参数
    //tongue twister
    g_hTongue_speedmult = CreateConVar(
        "l4d_perkmod_tonguetwister_speedmultiplier" ,
        "12.5" ,
        "SMOKER夺命速舌技能舌头发射速度 (设定值范围 1.0 < 5.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hTongue_speedmult, Convar_TongueSpeed);
    g_flTongue_speedmult = 12.5;

    g_hTongue_pullmult = CreateConVar(
        "l4d_perkmod_tonguetwister_pullmultiplier" ,
        "8.10" ,
        "SMOKER夺命速舌技能拉取人类的速度 (设定值范围 1.0 < 5.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hTongue_pullmult, Convar_TonguePull);
    g_flTongue_pullmult = 8.10;

    g_hTongue_rangemult = CreateConVar(
        "l4d_perkmod_tonguetwister_rangemultiplier" ,
        "2.30" ,
        "SMOKER夺命速舌技能射程距离的有效范围 (设定值范围 1.0 < 5.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hTongue_rangemult, Convar_TongueRange);
    g_flTongue_rangemult = 2.30;

    g_hTongue_enable = CreateConVar(
        "l4d_perkmod_tonguetwister_enable" ,
        "1" ,
        "SMOKER夺命速舌技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hTongue_enable, Convar_Tongue_en);
    g_iTongue_enable = 1;

    //squeezer
    g_hSqueezer_dmgmult = CreateConVar(
        "l4d_perkmod_squeezer_damagemultiplier" ,
        "7.3" ,
        "SMOKER致命之怀技能对幸存者所造成的伤害比率 (设定值范围 0.01 < 4.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSqueezer_dmgmult, Convar_Squeezer);
    g_flSqueezer_dmgmult = 7.3;

    g_hSqueezer_enable = CreateConVar(
        "l4d_perkmod_squeezer_enable" ,
        "1" ,
        "SMOKER致命之怀技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSqueezer_enable, Convar_Squeezer_en);
    g_iSqueezer_enable = 1;

    //drag and drop
    g_hDrag_cdmult = CreateConVar(
        "l4d_perkmod_draganddrop_timemultiplier" ,
        "0.1" ,
        "SMOKER控舌技能的冷却时间 (设定值范围 0.01 < 1.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDrag_cdmult, Convar_Drag);
    g_flDrag_cdmult = 0.2;

    g_hDrag_enable = CreateConVar(
        "l4d_perkmod_draganddrop_enable" ,
        "1" ,
        "SMOKER控舌技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDrag_enable, Convar_Drag_en);
    g_iDrag_enable = 1;

    //smoke it
    g_hSmokeItSpeed = CreateConVar(
        "l4d_perkmod_smokeit_speed" ,
        "0.79" ,
        "SMOKER专业拖车拉人移动的速度" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    g_flSmokeItSpeed = 0.79;
    HookConVarChange(g_hSmokeItSpeed, Convar_SmokeIt_speed);

    g_hSmokeItMaxRange = CreateConVar(
        "l4d_perkmod_smokeit_tonguestretch" ,
        "4200" ,
        "SMOKER舌头拉人的最大距离，超过即会放开幸存者" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    g_iSmokeItMaxRange = 4200;
    HookConVarChange(g_hSmokeItMaxRange, Convar_SmokeIt_range);

    g_hSmokeIt_enable = CreateConVar(
        "l4d_perkmod_smokeit_enable" ,
        "1" ,
        "专业拖车技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSmokeIt_enable, Convar_SmokeIt_en);
    g_iSmokeIt_enable = 1;



    //HUNTER  猎手技能参数
    //body slam
    g_hBody_minbound = CreateConVar(
        "l4d_perkmod_bodyslam_minbound" ,
        "220" ,
        "HUNTER鬼影之锤技能对幸存者所造成的伤害值 (设定值范围 2 < 100)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hBody_minbound, Convar_Body);
    g_iBody_minbound = 220;

    g_hBody_enable = CreateConVar(
        "l4d_perkmod_bodyslam_enable" ,
        "1" ,
        "HUNTER鬼影之锤技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hBody_enable, Convar_Body_en);
    g_iBody_enable = 1;

    //efficient killer
    g_hEfficient_dmgmult = CreateConVar(
        "l4d_perkmod_efficientkiller_damagemultiplier" ,
        "1.95" ,
        "HUNTER夺命杀手技能的伤害设定 (设定值范围 0.01 < 4.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hEfficient_dmgmult, Convar_Eff);
    g_flEfficient_dmgmult = 1.95;

    g_hEfficient_enable = CreateConVar(
        "l4d_perkmod_efficientkiller_enable" ,
        "1" ,
        "HUNTER夺命杀手技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hEfficient_enable, Convar_Eff_en);
    g_iEfficient_enable = 1;

    //grasshopper
    g_hGrass_rate = CreateConVar(
        "l4d_perkmod_grasshopper_rate" ,
        "1.93" ,
        "HUNTER暗黑之忍技能的扑越距离倍数 (设定值范围 1.0 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hGrass_rate, Convar_Grass);
    g_flGrass_rate = 1.93;

    g_hGrass_enable = CreateConVar(
        "l4d_perkmod_grasshopper_enable" ,
        "1" ,
        "HUNTER暗黑之忍技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hGrass_enable, Convar_Grass_en);
    g_iGrass_enable = 1;

    //speed demon
    g_hSpeedDemon_rate = CreateConVar(
        "l4d_perkmod_speeddemon_rate" ,
        "0.35" ,
        "HUNTER夺命印记技能行走速度倍数 (设定值范围 1.0 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpeedDemon_rate, Convar_Demon);
    g_flSpeedDemon_rate = 0.35;    //可以为0.5 就是减速度

    g_hSpeedDemon_dmgmult = CreateConVar(
        "l4d_perkmod_speeddemon_damagemultiplier" ,
        "19.5" ,
        "HUNTER夺命印记技能所造成的伤害倍数 (设定值范围 0.01 < 4.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpeedDemon_dmgmult, Convar_Demon_dmg);
    g_flSpeedDemon_dmgmult = 19.5;

    g_hSpeedDemon_enable = CreateConVar(
        "l4d_perkmod_speeddemon_enable" ,
        "1" ,
        "HUNTER夺命印记技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSpeedDemon_enable, Convar_Demon_en);
    g_iSpeedDemon_enable = 1;



    //TANK tank技能参数
    //adrenal glands            疯狂暴走
    g_hAdrenal_punchcdmult = CreateConVar(// CD缩短比率
        "l4d_perkmod_adrenalglands_punchcooldownmultiplier" ,
        "0.50" ,
        "坦克疯狂暴走技能的拳击速度 (设定值范围 0.01 < 1.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hAdrenal_punchcdmult, Convar_Adrenalpunchcd);
    g_flAdrenal_punchcdmult = 0.50;

    g_hAdrenal_throwcdmult = CreateConVar( // 扔石头CD
        "l4d_perkmod_adrenalglands_throwcooldownmultiplier" ,
        "0.27" ,
        "坦克疯狂暴走技能的投石头速度 (设定值范围 0.01 < 1.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hAdrenal_throwcdmult, Convar_Adrenalthrowcd);
    g_flAdrenal_throwcdmult = 0.27;    // 0.9几乎不会有冷却时间

    g_hAdrenal_enable = CreateConVar(// 是否允许玩家选择此技能
        "l4d_perkmod_adrenalglands_enable" ,
        "1" ,
        "坦克疯狂暴走技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hAdrenal_enable, Convar_Adrenal_en);
    g_iAdrenal_enable = 1;

    //juggernaut 魔王重生
    g_hJuggernaut_hp = CreateConVar(// tank血量
        "l4d_perkmod_juggernaut_health" ,
        "26000" ,
        "坦克魔王重生技能增加的血量 (设定血量值范围 1 < 99999)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hJuggernaut_hp, Convar_Jugg);
    g_iJuggernaut_hp = 30000;

    g_hJuggernaut_enable = CreateConVar(// 是否允许玩家选择此技能
        "l4d_perkmod_juggernaut_enable" ,
        "1" ,
        "坦克魔王重生技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hJuggernaut_enable, Convar_Jugg_en);
    g_iJuggernaut_enable = 1;

    //metabolic boost  神行百变
    g_hMetabolic_speedmult = CreateConVar(// 增加速度比率
        "l4d_perkmod_metabolicboost_speedmultiplier" ,
        "1.58" ,
        "坦克神行百变技能增加的速度倍数 (设定值范围 1.01 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMetabolic_speedmult, Convar_Met);
    g_flMetabolic_speedmult = 1.58;

    g_hMetabolic_enable = CreateConVar(// 是否允许玩家选择此技能
        "l4d_perkmod_metabolicboost_enable" ,
        "1" ,
        "坦克神行百变技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMetabolic_enable, Convar_Met_en);
    g_iMetabolic_enable = 1;

    //storm caller 王者召唤
    g_hStorm_mobcount = CreateConVar(// 召唤妹子数量
        "l4d_perkmod_stormcaller_mobcount" ,
        "0" ,
        "坦克王者召唤技能产生的特殊感染者数量 (0=1只 1=2只 2=3只...)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hStorm_mobcount, Convar_Storm);
    g_iStorm_mobcount = 1;

    g_hStorm_enable = CreateConVar(// 是否允许玩家选择此技能
        "l4d_perkmod_stormcaller_enable" ,
        "1" ,
        "坦克王者召唤技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hStorm_enable, Convar_Storm_en);
    g_iStorm_enable = 1;

    //double the trouble 分身
    g_hDouble_hpmult = CreateConVar(// 血量比例
        "l4d_perkmod_doublethetrouble_healthmultiplier" ,
        "0.65" ,
        "坦克分身术技能给予分身坦克的血量百分比 (设定值范围 0.1 < 2.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDouble_hpmult, Convar_Double);
    g_flDouble_hpmult = 0.65;

    g_hDouble_enable = CreateConVar(// 是否允许玩家选择此技能
        "l4d_perkmod_doublethetrouble_enable" ,
        "1" ,
        "坦克分身术技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hDouble_enable, Convar_Double_en);
    g_iDouble_enable = 1;



    //JOCKEY
    //ride like the wind
    g_hWind_rate = CreateConVar(
        "l4d_perkmod_ridelikethewind_rate" ,
        "3.4" ,
        "JOCKEY专业车手技能乘骑在幸存者身上所具有的移动速度 (设定值范围 1.0 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hWind_rate, Convar_Wind);
    g_flWind_rate = 3.4;

    g_hWind_enable = CreateConVar(
        "l4d_perkmod_ridelikethewind_enable" ,
        "1" ,
        "JOCKEY专业车手技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hWind_enable, Convar_Wind_en);
    g_iWind_enable = 1;

    //cavalier
    g_hCavalier_hpmult = CreateConVar(
        "l4d_perkmod_cavalier_healthmultiplier" ,
        "21.00" ,
        "JOCKEY血气旺盛技能增加自身的血量倍数 (设定值范围 0.01 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hCavalier_hpmult, Convar_Cavalier);
    g_flCavalier_hpmult = 33.00;

    g_hCavalier_enable = CreateConVar(
        "l4d_perkmod_cavalier_enable" ,
        "1" ,
        "JOCKEY血气旺盛技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hCavalier_enable, Convar_Cavalier_en);
    g_iCavalier_enable = 1;

    //frogger
    g_hFrogger_dmgmult = CreateConVar(
        "l4d_perkmod_frogger_damagemultiplier" ,
        "9.7" ,
        "JOCKEY嗜血之鞍技能坐骑幸存者的基础伤害倍数 (设定值范围 0.01 < 4.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hFrogger_dmgmult, Convar_Frogger_dmgmult);
    g_flFrogger_dmgmult = 9.7;

    g_hFrogger_rate = CreateConVar(
        "l4d_perkmod_frogger_rate" ,
        "1.95" ,
        "JOCKEY嗜血之鞍技能的跳跃范围倍数 (设定值范围 1.0 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hFrogger_rate, Convar_Frogger_rate);
    g_flFrogger_rate = 1.95;

    g_hFrogger_enable = CreateConVar(
        "l4d_perkmod_frogger_enable" ,
        "1" ,
        "JOCKEY嗜血之鞍技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hFrogger_enable, Convar_Frogger_en);
    g_iFrogger_enable = 1;

    //ghost rider
    g_hGhost_alpha = CreateConVar(
        "l4d_perkmod_ghostrider_alpha" ,
        "0" ,
        "JOCKEY幽灵幻术技能的设定 (设定值范围 0  < 255 ,说明: 0=完全透明 ,255=不透明,数值高低决定偏向身体不透明或透明的状态)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hGhost_alpha, Convar_Ghost);
    g_iGhost_alpha = 0;

    g_hGhost_enable = CreateConVar(
        "l4d_perkmod_ghostrider_enable" ,
        "1" ,
        "JOCKEY幽灵幻术技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hGhost_enable, Convar_Ghost_en);
    g_iGhost_enable = 1;



    //SPITTER
    //twin spitfire
    g_hTwinSF_delay = CreateConVar(
        "l4d_perkmod_twinspitfire_delay" ,
        "1.0" ,
        "SPITTER五重喷射技能的冷却时间 (设定值范围 0.5 < 20.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hTwinSF_delay, Convar_TwinSF);
    g_flTwinSF_delay = 0.5;

    g_hTwinSF_enable = CreateConVar(
        "l4d_perkmod_twinspitfire_enable" ,
        "1" ,
        "SPITTER五重喷射技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hTwinSF_enable, Convar_TwinSF_en);
    g_iTwinSF_enable = 1;

    //mega adhesive
    g_hMegaAd_enable = CreateConVar(
        "l4d_perkmod_megaadhesive_enable" ,
        "1" ,
        "SPITTER粘性毒液技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMegaAd_enable, Convar_MegaAd_en);
    g_iMegaAd_enable = 1;

    g_hMegaAd_slow = CreateConVar(
        "l4d_perkmod_megaadhesive_slowmultiplier" ,
        "0.07" ,
        "SPITTER粘性毒液技能将幸存者移动速度降成原速的倍数(设定值范围 0.1 < 1.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hMegaAd_slow, Convar_MegaAd);
    g_flMegaAd_slow = 0.07;



    //CHARGER
    //scattering ram
    g_hScatter_force = CreateConVar(
        "l4d_perkmod_scatteringram_force" ,
        "1.60" ,
        "CHARGER战神之力技能的粘带装人的力度（撞飞N远） (设定值范围 1.0 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hScatter_force, Convar_Scatter_force);
    g_flScatter_force = 1.60;

    g_hScatter_hpmult = CreateConVar(
        "l4d_perkmod_scatteringram_healthmultiplier" ,
        "1.00" ,
        "CHARGER战神之力学习后的血量倍数 (设定值范围 0.01 < 3.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hScatter_hpmult, Convar_Scatter_hpmult);
    g_flScatter_hpmult = 1.00;

    g_hScatter_enable = CreateConVar(
        "l4d_perkmod_scatteringram_enable" ,
        "1" ,
        "CHARGER战神之力技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hScatter_enable, Convar_Scatter_en);
    g_iScatter_enable = 1;

    //speeding bullet
    g_hBullet_rate = CreateConVar(
        "l4d_perkmod_speedingbullet_rate" ,
        "5.0" ,
        "CHARGER极速冲击技能的速度比率及间隔时间 (设定值范围 1.0 < 10.0)" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hBullet_rate, Convar_Bullet);
    g_flBullet_rate = 5.0;

    g_hBullet_enable = CreateConVar(
        "l4d_perkmod_speedingbullet_enable" ,
        "1" ,
        "SCHARGER极速冲击技能是否启用, 0=禁用, 1=启用" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hBullet_enable, Convar_Bullet_en);
    g_iBullet_enable = 1;



    //MISC              机器技能
    //bot preferences for perks
    g_hBot_Sur1 = CreateConVar(
        "l4d_perkmod_bot_survivor1" ,
        "1,2,3" ,
        "幸存者机器人可选择的主技能: 1 = 枪神附身, 2 = 极速上弹, 3 = 近战专家" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Sur2 = CreateConVar(
        "l4d_perkmod_bot_survivor2" ,
        "1,2,3" ,
        "幸存者机器人可选择的副技能: 1 = 医疗专家, 2 = 黄金战甲, 3 = 救护医师" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Sur3 = CreateConVar(
        "l4d_perkmod_bot_survivor3" ,
        "1,2" ,
        "幸存者机器人可选择的参技能: 1 = 弹药专家, 2 = 坚韧之躯" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Inf1 = CreateConVar(
        "l4d_perkmod_bot_boomer" ,
        "1,3" ,
        "Boomer机器人可选择的技能: 1 = 恶魔唤醒, 2 = 遗忘之咒, 3 = 致命袭击" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Inf3 = CreateConVar(
        "l4d_perkmod_bot_smoker" ,
        "1,2" ,
        "Smoker机器人可选择的技能: 1 = 夺命速舌, 2 = 致命之怀, 3 = 控舌" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Inf4 = CreateConVar(
        "l4d_perkmod_bot_hunter" ,
        "1,2" ,
        "Hunter机器人选择的技能: 1 = 鬼影之锤, 2 = 夺命杀手" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Inf2 = CreateConVar(
        "l4d_perkmod_bot_tank" ,
        "1,2,3,4,5" ,
        "Tank机器人选择的技能: 1 = 疯狂暴走, 2 = 魔王重生, 3 = 神行百变, 4 = 王者召唤, 5 = 分身术" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Inf5 = CreateConVar(
        "l4d_perkmod_bot_jockey" ,
        "1,2,3,4" ,
        "Jockey机器人选择的技能: 1 = 专业车手, 2 = 血气旺盛, 3 = 嗜血之鞍, 4 = 幽灵幻术" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Inf6 = CreateConVar(
        "l4d_perkmod_bot_spitter" ,
        "1,2" ,
        "Spitter机器人选择的技能: 1 = 五重喷射 2 = 粘性毒液" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    g_hBot_Inf7 = CreateConVar(
        "l4d_perkmod_bot_charger" ,
        "1,2" ,
        "Charger机器人选择的技能: 1 = 战神之力, 2 = 极速冲击" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );

    //default perks 缺省默认技能
    g_hSur1_default = CreateConVar(
        "l4d_perkmod_default_survivor1" ,
        "1" ,
        "幸存者主技能预设选项: 1 = 枪神附身, 2 = 夺命速射, 3 = 极速上弹, 4 = 爆破专家, 5 = 近战专家" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSur1_default, Convar_Def_Sur1);
    g_iSur1_default = 1;

    g_hSur2_default = CreateConVar(
        "l4d_perkmod_default_survivor2" ,
        "1" ,
        "幸存者副技能预设选项: 1 = 医疗专家, 2 = 黄金战甲, 3 = 救护医师" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSur2_default, Convar_Def_Sur2);
    g_iSur2_default = 1;

    g_hSur3_default = CreateConVar(
        "l4d_perkmod_default_survivor3" ,
        "4" ,
        "幸存者参技能预设选项: 1 = 弹药专家, 2 = 枪支专家, 3 = 坚韧之躯 4 = 凌波微步 5 = 棒球队员" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSur3_default, Convar_Def_Sur3);
    g_iSur3_default = 4;

    g_hInf1_default = CreateConVar(
        "l4d_perkmod_default_boomer" ,
        "4" ,
        "Boomer预设技能选项: 1 = 恶魔唤醒, 2 = 遗忘之咒, 3 = 致命袭击, 4 = 身体轻盈" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf1_default, Convar_Def_Inf1);
    g_iInf1_default = 4;

    g_hInf2_default = CreateConVar(
        "l4d_perkmod_default_tank" ,
        "3" ,
        "Tank预设技能选项: 1 = 疯狂暴走, 2 = 魔王重生, 3 = 神行百变, 4 = 王者召唤, 5 = 分身术" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf2_default, Convar_Def_Inf2);
    g_iInf2_default = 5;

    g_hInf3_default = CreateConVar(
        "l4d_perkmod_default_smoker" ,
        "3" ,
        "Smoker预设技能选项: 1 = 夺命速舌, 2 = 致命之怀, 3 = 控舌, 4=专业拖车" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf3_default, Convar_Def_Inf3);
    g_iInf3_default = 3;

    g_hInf4_default = CreateConVar(
        "l4d_perkmod_default_hunter" ,
        "1" ,
        "Hunter预设技能选项: 1 = 鬼影之锤, 2 = 夺命杀手, 3 = 暗黑之忍, 4 = 夺命印记" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf4_default, Convar_Def_Inf4);
    g_iInf4_default = 1;

    g_hInf5_default = CreateConVar(
        "l4d_perkmod_default_jockey" ,
        "2" ,
        "Jockey预设技能: 1 = 专业车手，2 = 血气旺盛，3 = 嗜血之鞍，4 = 幽灵幻术，" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf5_default, Convar_Def_Inf5);
    g_iInf5_default = 2;

    g_hInf6_default = CreateConVar(
        "l4d_perkmod_default_spitter" ,
        "2" ,
        "Spitter预设技能: 1 = 五重喷射, 2 = 减速酸液" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf6_default, Convar_Def_Inf6);
    g_iInf6_default = 2;

    g_hInf7_default = CreateConVar(
        "l4d_perkmod_default_charger" ,
        "2" ,
        "Charger预设技能: 1 = 战神之力, 2 = 极速冲击" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf7_default, Convar_Def_Inf7);
    g_iInf7_default = 2;



    //enable perk trees         技能列表可用
    //-----------------
    g_hSur1_enable = CreateConVar(
        "l4d_perkmod_perktree_survivor1_enable" ,
        "1" ,
        "服务器是否禁止玩家使用幸存者主技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSur1_enable, Convar_Sur1_en);
    g_iSur1_enable = 1;

    g_hSur2_enable = CreateConVar(
        "l4d_perkmod_perktree_survivor2_enable" ,
        "1" ,
        "服务器是否禁止玩家使用幸存者副技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSur2_enable, Convar_Sur2_en);
    g_iSur2_enable = 1;

    g_hSur3_enable = CreateConVar(
        "l4d_perkmod_perktree_survivor3_enable" ,
        "1" ,
        "服务器是否禁止玩家使用幸存者参技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hSur3_enable, Convar_Sur3_en);
    g_iSur3_enable = 1;

    g_hInf1_enable = CreateConVar(
        "l4d_perkmod_perktree_boomer_enable" ,
        "1" ,
        "服务器是否禁止玩家使用感染者boomer技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf1_enable, Convar_Inf1_en);
    g_iInf1_enable = 1;

    g_hInf2_enable = CreateConVar(
        "l4d_perkmod_perktree_tank_enable" ,
        "1" ,
        "服务器是否禁止玩家使用感染者Tank技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf2_enable, Convar_Inf2_en);
    g_iInf2_enable = 0;

    g_hInf3_enable = CreateConVar(
        "l4d_perkmod_perktree_smoker_enable" ,
        "1" ,
        "服务器是否禁止玩家使用感染者smoker技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf3_enable, Convar_Inf3_en);
    g_iInf3_enable = 1;

    g_hInf4_enable = CreateConVar(
        "l4d_perkmod_perktree_hunter_enable" ,
        "1" ,
        "服务器是否禁止玩家使用感染者hunter技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf4_enable, Convar_Inf4_en);
    g_iInf4_enable = 1;

    g_hInf5_enable = CreateConVar(
        "l4d_perkmod_perktree_jockey_enable" ,
        "1" ,
        "服务器是否禁止玩家使用感染者jockey技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf5_enable, Convar_Inf5_en);
    g_iInf5_enable = 1;

    g_hInf6_enable = CreateConVar(
        "l4d_perkmod_perktree_spitter_enable" ,
        "1" ,
        "服务器是否禁止玩家使用感染者spitter技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf6_enable, Convar_Inf6_en);
    g_iInf6_enable = 1;

    g_hInf7_enable = CreateConVar(
        "l4d_perkmod_perktree_charger_enable" ,
        "1" ,
        "服务器是否禁止玩家使用感染者charger技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hInf7_enable, Convar_Inf7_en);
    g_iInf7_enable = 1;



    //force random perks            随机技能
    g_hForceRandom = CreateConVar(
        "l4d_perkmod_forcerandomperks" ,
        "0" ,
        "服务器是否禁止玩家自订技能.  0=否  1=是" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hForceRandom, Convar_ForceRandom);
    g_iForceRandom = 0;

    //enable random perk choice
    g_hRandomEnable = CreateConVar(
        "l4d_perkmod_randomperks_enable" ,
        "0" ,
        "服务器是否禁止玩家在每回合开始时随意选择技能或不使用技能.  0=是  1=否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
    HookConVarChange(g_hRandomEnable, Convar_Random_en);
    g_iRandomEnable = 0;

    //misc game convars 自动显示技能菜单
    g_hMenuAutoShow_enable = CreateConVar(
        "l4d_perkmod_autoshowmenu" ,
        "1" ,
        "技能菜单将会显示在每一轮开始. 1 = 是, 2 = 否" ,
        FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY );
}
//=============================
// ConVar Changes
//=============================


//changes in base L4D convars
//---------------------------

//tracks changes in game mode  游戏模式，合作，生存，对抗
public Convar_GameMode (Handle:convar, const String:oldValue[], const String:newValue[])
{
    if (StrContains(newValue,"versus",false)!= -1
        || StrContains(newValue,"scavenge",false)!= -1)
        g_iL4D_GameMode=2;
    else if (StrEqual(newValue,"survival",false)==true)
        g_iL4D_GameMode=1;
    else
        g_iL4D_GameMode=0;

    //----DEBUG----
    //PrintToChatAll("\x03gamemode change detected, new value: \x01%i",g_iL4D_GameMode);
}


//changes in perkmod convars
//--------------------------- 修改参数函数集，主要判定是否在范围之内

//stopping power            枪神
//the enable/disable functions also call
//the checks-pre-calculate function
public Convar_Stopping (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.05)
        flF=0.05;
    else if (flF>1.0)
        flF=1.0;
    g_flStopping_dmgmult = flF;
}

public Convar_Stopping_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iStopping_enable = iI;

    Stopping_RunChecks();
}

public Convar_Stopping_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iStopping_enable_sur = iI;

    Stopping_RunChecks();
}

public Convar_Stopping_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iStopping_enable_vs = iI;

    Stopping_RunChecks();
}

//spirit    黄金战甲
public Convar_SpiritBuff (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<0)
        iI=0;
    else if (iI>170)            //黄金战甲倒地后起身恢复的血量（默认是170）
        iI=170;
    g_iSpirit_buff = iI;
}

public Convar_SpiritCD (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>1800)
        iI=1800;
    g_iSpirit_cd = iI;
}

public Convar_SpiritCDsur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>1800)
        iI=1800;
    g_iSpirit_cd_sur = iI;
}

public Convar_SpiritCDvs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>1800)
        iI=1800;
    g_iSpirit_cd_vs = iI;
}

public Convar_Spirit_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSpirit_enable = iI;
}

public Convar_Spirit_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSpirit_enable_sur = iI;
}

public Convar_Spirit_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSpirit_enable_vs = iI;
}

//helping hand    救护专家
public Convar_HelpTime (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.01)
        flF=0.01;
    else if (flF>1.0)
        flF=1.0;
    g_flHelpHand_timemult = flF;
}

public Convar_HelpBuff (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>170)
        iI=170;
    g_iHelpHand_buff = iI;
}

public Convar_HelpBuffvs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>170)
        iI=170;
    g_iHelpHand_buff_vs = iI;
}

public Convar_Help_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iHelpHand_enable = iI;
}

public Convar_Help_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iHelpHand_enable_sur = iI;
}

public Convar_Help_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iHelpHand_enable_vs = iI;
}

public Convar_Help_convar (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iHelpHand_convar = iI;
}

//unbreakable       医疗专家
public Convar_Unbreak (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>1000)            //这2行是控制医疗专家血上限的，设置后可配合CFG参数进行设置
        iI=1000;                //这2行是控制医疗专家血上限的，设置后可配合CFG参数进行设置
    g_iUnbreak_hp = iI;
}

public Convar_Unbreak_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iUnbreak_enable = iI;
}

public Convar_Unbreak_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iUnbreak_enable_sur = iI;
}

public Convar_Unbreak_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iUnbreak_enable_vs = iI;
}

//double tap                夺命射速
//the enable/disable functions also call
//for the run-on-game-frame-check function
public Convar_DT (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.02)
        flF=0.02;
    else if (flF>0.9)
        flF=0.9;
    g_flDT_rate = flF;
}

public Convar_DT_freechance (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.0)
        flF=0.0;
    else if (flF>0.5)
        flF=0.5;
    g_flDT_freechance = flF;
}

public Convar_DT_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iDT_enable = iI;

    DT_RunChecks();
}

public Convar_DT_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iDT_enable_sur = iI;

    DT_RunChecks();
}

public Convar_DT_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iDT_enable_vs = iI;

    DT_RunChecks();
}

//sleight of hand               急速上弹
public Convar_SoH (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.02)
        flF=0.02;
    else if (flF>0.9)
        flF=0.9;
    g_flSoH_rate = flF;
}

public Convar_SoH_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSoH_enable = iI;
}

public Convar_SoH_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSoH_enable_sur = iI;
}

public Convar_SoH_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSoH_enable_vs = iI;
}

//chem reliant          枪支专家
public Convar_Chem (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>150)
        iI=150;
    g_iChem_buff = iI;
}

public Convar_Chem_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iChem_enable = iI;
}

public Convar_Chem_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iChem_enable_sur = iI;
}

public Convar_Chem_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iChem_enable_vs = iI;
}

//pyrotechnician            爆破专家
public Convar_Pyro_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iPyro_enable = iI;
}

public Convar_Pyro_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iPyro_enable_sur = iI;
}

public Convar_Pyro_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iPyro_enable_vs = iI;
}

//pack rat              弹药专家
public Convar_Pack (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.01)
        flF=0.01;
    else if (flF>1.0)
        flF=1.0;
    g_flPack_ammomult = flF;
}

public Convar_Pack_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iPack_enable = iI;
}

public Convar_Pack_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iPack_enable_sur = iI;
}

public Convar_Pack_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iPack_enable_vs = iI;
}

//hard to kill          坚韧之躯
public Convar_Hard (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.01)
        flF=0.01;
    else if (flF>3.0)
        flF=3.0;
    g_flHard_hpmult = flF;
}

public Convar_Hard_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iHard_enable = iI;
}

public Convar_Hard_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iHard_enable_sur = iI;
}

public Convar_Hard_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iHard_enable_vs = iI;
}

//martial artist
//also rebuilds MA registry in order to
//reassign new speed values
public Convar_MA_maxpenalty (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<0)
        iI=0;
    else if (iI>6)
        iI=6;
    g_iMA_maxpenalty = iI;
    MA_Rebuild();
}

public Convar_MA_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iMA_enable = iI;
}

public Convar_MA_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iMA_enable_sur = iI;
}

public Convar_MA_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iMA_enable_vs = iI;
}

//extreme conditioning
public Convar_Extreme (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>1.5)
        flF=1.5;
    g_flExtreme_rate = flF;
    Extreme_Rebuild();
}

public Convar_Extreme_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iExtreme_enable = iI;
}

public Convar_Extreme_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iExtreme_enable_sur = iI;
}

public Convar_Extreme_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iExtreme_enable_vs = iI;
}

//little leaguer
public Convar_Little_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iLittle_enable = iI;
}

public Convar_Little_en_sur (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iLittle_enable_sur = iI;
}

public Convar_Little_en_vs (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iLittle_enable_vs = iI;
}

//barf bagged
public Convar_Barf_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iBarf_enable = iI;
}

//blind luck
public Convar_Blind (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.01)
        flF=0.01;
    else if (flF>1.0)
        flF=1.0;
    g_flBlind_cdmult = flF;
}

public Convar_Blind_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iBlind_enable = iI;
}

//dead wreckening
public Convar_Dead (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.01)
        flF=0.01;
    else if (flF>4.0)
        flF=4.0;
    g_flDead_dmgmult = flF;
}

public Convar_Dead_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iDead_enable = iI;
}

//motion sickness
public Convar_Motion (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>4.0)
        flF=4.0;
    g_flMotion_rate = flF;
}

public Convar_Motion_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iMotion_enable = iI;
}

//tongue twister
public Convar_TongueSpeed (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>5.0)
        flF=5.0;
    g_flTongue_speedmult = flF;
}

public Convar_TonguePull (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>5.0)
        flF=5.0;
    g_flTongue_pullmult = flF;
}

public Convar_TongueRange (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>5.0)
        flF=5.0;
    g_flTongue_rangemult = flF;
}

public Convar_Tongue_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iTongue_enable = iI;
}

//squeezer
public Convar_Squeezer (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.1)
        flF=0.1;
    else if (flF>4.0)
        flF=4.0;
    g_flSqueezer_dmgmult = flF;
}

public Convar_Squeezer_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSqueezer_enable = iI;
}

//drag and drop
public Convar_Drag (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.01)
        flF=0.01;
    else if (flF>1.0)
        flF=1.0;
    g_flDrag_cdmult = flF;
}

public Convar_Drag_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iDrag_enable = iI;
}

//smoke it
public Convar_SmokeIt_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSmokeIt_enable = iI;
}

public Convar_SmokeIt_range (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    g_iSmokeItMaxRange = iI;
}

public Convar_SmokeIt_speed (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    g_flSmokeItSpeed = flF;
}

//efficient killer
public Convar_Eff (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.1)
        flF=0.1;
    else if (flF>4.0)
        flF=4.0;
    g_flEfficient_dmgmult = flF;
}

public Convar_Eff_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iEfficient_enable = iI;
}

//body slam
public Convar_Body (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<2)
        iI=2;
    else if (iI>100)
        iI=100;
    g_iBody_minbound = iI;
}

public Convar_Body_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iBody_enable = iI;
}

//grasshopper
public Convar_Grass (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>3.0)
        flF=3.0;
    g_flGrass_rate = flF;
}

public Convar_Grass_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iGrass_enable = iI;
}

//speed demon
public Convar_Demon (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>3.0)
        flF=3.0;
    g_flSpeedDemon_rate = flF;
}

public Convar_Demon_dmg (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.1)
        flF=0.1;
    else if (flF>4.0)
        flF=4.0;
    g_flSpeedDemon_dmgmult = flF;
}

public Convar_Demon_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iSpeedDemon_enable = iI;
}

//ride like the wind 速猴
public Convar_Wind (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>3.0)
        flF=3.0;
    g_flWind_rate = flF;
}

public Convar_Wind_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iWind_enable = iI;
}

//cavalier
public Convar_Cavalier (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.0)
        flF=0.0;
    else if (flF>3.0)
        flF=3.0;
    g_flCavalier_hpmult = flF;
}

public Convar_Cavalier_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iCavalier_enable = iI;
}

//frogger 猴子蛙跳
public Convar_Frogger_dmgmult (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.0)
        flF=0.0;
    else if (flF>3.0)
        flF=3.0;
    g_flFrogger_dmgmult = flF;
}
// 骑行速率
public Convar_Frogger_rate (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>3.0)
        flF=3.0;
    g_flFrogger_rate = flF;
}

public Convar_Frogger_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iFrogger_enable = iI;
}

//ghost rider
public Convar_Ghost (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<0)
        iI=0;
    else if (iI>255)
        iI=255;
    g_iGhost_alpha = iI;
}

public Convar_Ghost_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iGhost_enable = iI;
}

//twin spitfire 口水
public Convar_TwinSF (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.5)
        flF=0.5;
    else if (flF>20.0)
        flF=20.0;
    g_flTwinSF_delay = flF;
}

public Convar_TwinSF_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iTwinSF_enable = iI;
}

//mega adhesive 粘性
public Convar_MegaAd_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iMegaAd_enable = iI;
}

public Convar_MegaAd (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.0)
        flF=0.0;
    else if (flF>3.0)
        flF=3.0;
    g_flMegaAd_slow = flF;
}


//scattering ram
public Convar_Scatter_force (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>3.0)
        flF=3.0;
    g_flScatter_force = flF;
}

public Convar_Scatter_hpmult (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.0)
        flF=0.0;
    else if (flF>3.0)
        flF=3.0;
    g_flScatter_hpmult = flF;
}

public Convar_Scatter_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iScatter_enable = iI;
}


//speeding bullet 子弹速度
public Convar_Bullet (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.0)
        flF=1.0;
    else if (flF>10.0)
        flF=10.0;
    g_flBullet_rate = flF;
}

public Convar_Bullet_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iBullet_enable = iI;
}


//adrenal glands 疯狂暴走
public Convar_Adrenalpunchcd (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.01)
        flF=0.01;
    else if (flF>1.0)
        flF=1.0;
    g_flAdrenal_punchcdmult = flF;
}

public Convar_Adrenalthrowcd (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.01)
        flF=0.01;
    else if (flF>1.0)
        flF=1.0;
    g_flAdrenal_throwcdmult = flF;
}

public Convar_Adrenal_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iAdrenal_enable = iI;
}

//juggernaut            魔王重生
public Convar_Jugg (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>99999)
        iI=99999;
    g_iJuggernaut_hp = iI;
}

public Convar_Jugg_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iJuggernaut_enable = iI;
}

//metabolic boost           神行百变
public Convar_Met (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<1.01)
        flF=1.01;
    else if (flF>3.0)
        flF=3.0;
    g_flMetabolic_speedmult = flF;
}

public Convar_Met_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iMetabolic_enable = iI;
}

//storm caller          王者召唤
public Convar_Storm (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<1)
        iI=1;
    else if (iI>10)
        iI=10;
    g_iStorm_mobcount = iI;
}

public Convar_Storm_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iStorm_enable = iI;
}

//double the trouble            分身
public Convar_Double (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new Float:flF=StringToFloat(newValue);
    if (flF<0.1)
        flF=0.1;
    else if (flF>2.0)
        flF=2.0;
    g_flDouble_hpmult = flF;
}

public Convar_Double_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;
    g_iDouble_enable = iI;
}

//default perks         默认技能
public Convar_Def_Sur1 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>5)
        iI=5;

    g_iSur1_default=iI;
}

public Convar_Def_Sur2 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>3)
        iI=3;

    g_iSur2_default=iI;
}

public Convar_Def_Sur3 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>3)
        iI=3;

    g_iSur3_default=iI;
}

public Convar_Def_Inf1 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>3)
        iI=3;

    g_iInf1_default=iI;
}

public Convar_Def_Inf2 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>5)
        iI=5;

    g_iInf2_default=iI;
}

public Convar_Def_Inf3 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>3)
        iI=3;

    g_iInf3_default=iI;
}

public Convar_Def_Inf4 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>5)
        iI=5;

    g_iInf4_default=iI;
}

public Convar_Def_Inf5 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>5)
        iI=5;

    g_iInf5_default=iI;
}

public Convar_Def_Inf6 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>5)
        iI=5;

    g_iInf6_default=iI;
}

public Convar_Def_Inf7 (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI<=0)
        iI=1;
    else if (iI>5)
        iI=5;

    g_iInf7_default=iI;
}

//force random perks 随机技能
public Convar_ForceRandom (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iForceRandom=iI;
}

//enable random perk choice  是否可选随机技能
public Convar_Random_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iRandomEnable=iI;
}

//perk trees 技能列表
public Convar_Sur1_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iSur1_enable=iI;

    RunChecksAll();
}

public Convar_Sur2_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iSur2_enable=iI;
    RunChecksAll();
}

public Convar_Sur3_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iSur3_enable=iI;
    RunChecksAll();
}

public Convar_Inf1_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iInf1_enable=iI;
    RunChecksAll();
}

public Convar_Inf2_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iInf2_enable=iI;
    RunChecksAll();
}

public Convar_Inf3_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iInf3_enable=iI;
    RunChecksAll();
}

public Convar_Inf4_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iInf4_enable=iI;
    RunChecksAll();
}

public Convar_Inf5_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iInf5_enable=iI;
    RunChecksAll();
}

public Convar_Inf6_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iInf6_enable=iI;
    RunChecksAll();
}

public Convar_Inf7_en (Handle:convar, const String:oldValue[], const String:newValue[])
{
    new iI=StringToInt(newValue);
    if (iI==0)
        iI=0;
    else
        iI=1;

    g_iInf7_enable=iI;
    RunChecksAll();
}





//====================================================
//====================================================
//                    P    E    R    K    S       技能事件驱动源码
//====================================================
//====================================================


//=============================
// Events Directly related to perks
//=============================

//this trigger only runs on players, not common infected    幸存者技能
public Action:Event_PlayerHurtPre (Handle:event, const String:name[], bool:dontBroadcast)// 伤害事件
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"attacker"));
    new iVic=GetClientOfUserId(GetEventInt(event,"userid"));

    if (iVic==0) return Plugin_Continue;

    new iType=GetEventInt(event,"type");
    new iDmgOrig=GetEventInt(event,"dmg_health");

    //----DEBUG----
    //new String:sWeapon[128];
    //GetEventString(event,"weapon",sWeapon,128);
    //PrintToChatAll("\x03attacker:\x01%i\x03 weapon:\x01%s\x03 type:\x01%i\x03 amount: \x01%i",iAtt,sWeapon,iType,iDmgOrig);


    //check for dead wreckening damage add for zombies 僵尸伤害
    if (DeadWreckening_DamageAdd(iAtt,iVic,iType,iDmgOrig)==1)
        return Plugin_Continue;

    if (iAtt==0) return Plugin_Continue;

    new iTA=GetClientTeam(iAtt);
    decl String:stWpn[16];
    GetEventString(event,"weapon",stWpn,16);

    //----DEBUG----
    //if (iTA==2) PrintToChatAll("\x03weapon:\x01%s\x03 type:\x01%i",stWpn,iType);

    //if damage is from survivors to a non-survivor,
    //check for damage add (stopping power)                 枪神伤害
    if (Stopping_DamageAdd(iAtt,iVic,iTA,iDmgOrig,stWpn)==1)
        return Plugin_Continue;

    //otherwise, check for infected damage add types感染者伤害和类型
    //(body slam, efficient killer, squeezer)

    //...check for body slam鬼影之锤
    if (BodySlam_DamageAdd(iAtt,iVic,iTA,iType,stWpn,iDmgOrig)==1)
        return Plugin_Continue;

    //run speed demon checks夺命印记
    if (SpeedDemon_DamageAdd(iAtt,iVic,iTA,iType,stWpn,iDmgOrig)==1)
        return Plugin_Continue;

    //run efficient killer checks夺命杀手
    if (EfficientKiller_DamageAdd(iAtt,iVic,iTA,iType,stWpn,iDmgOrig)==1)
        return Plugin_Continue;

    //check for squeezer    致命之怀
    if (Squeezer_DamageAdd(iAtt,iVic,iTA,stWpn,iDmgOrig)==1)
        return Plugin_Continue;

    //run mega adhesive checks粘性毒液
    if (MegaAd_SlowEffect (iAtt, iVic, stWpn)==1)
        return Plugin_Continue;

    //run frogger checks嗜血之鞍
    if (Frogger_DamageAdd(iAtt,iVic,iTA,stWpn,iDmgOrig)==1)
        return Plugin_Continue;

    return Plugin_Continue;
}

public Event_Incap (Handle:event, const String:name[], bool:dontBroadcast)  // 倒地事件
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));

    if (iCid==0) return;

    if (GetClientTeam(iCid) == 2
        && GetEntData(iCid,g_iIncapO) != 0 )
        g_iPIncap[iCid]=1;

    HardToKill_OnIncap(iCid); // 坚韧之躯修正
}

//called when player is healed
public Event_PlayerHealed (Handle:event, const String:name[], bool:dontBroadcast) // 治疗事件
{
    new iCid=GetClientOfUserId(GetEventInt(event,"subject"));
    if (iCid==0 || g_iConfirm[iCid]==0) return;

    Unbreakable_OnHeal(iCid);       // 医疗专家修正
}

//called when survivor spawns from closet 营救幸存者（打开小屋）
public Event_PlayerRescued (Handle:event, const String:name[], bool:dontBroadcast)// 营救幸存事件
{
    new iCid=GetClientOfUserId(GetEventInt(event,"victim"));
    if (iCid==0 || g_iConfirm[iCid]==0)
        return;

    //reset vars related to spirit perk
    g_iMyDisabler[iCid] = -1;                                   // disable为-1
    g_iPIncap[iCid]=0;                                          // 倒地标准为0
    g_iSpiritCooldown[iCid]=0;                              // 黄金战甲CD为0
    //reset var related to blind luck perk                  // 重置被使用遗忘之咒技能
    SetEntProp(iCid, Prop_Send, "m_iHideHUD", 0);
    //rebuilds double tap registry                          // 射速时间修正
    CreateTimer(0.3,Delayed_Rebuild,0);

    //checks for unbreakable health bonus       // 医疗专家血量奖励修正
    Unbreakable_OnRescue(iCid);
    //check for pyrotechnician bonus grenade        // 爆破专家技能修正
    Event_Confirm_Grenadier (iCid);
    //check for chem reliant bonus pills                枪支专家技能修正
    Event_Confirm_ChemReliant(iCid);
}

//on game frame
public OnGameFrame()
{
    //if frames aren't being processed,
    //don't bother - otherwise we get LAG
    //or even disconnects on map changes, etc...
    
    if (IsServerProcessing()==false
        || g_bIsLoading == true
        || g_bIsRoundStart == true)
        return;

    DT_OnGameFrame();
    MA_OnGameFrame();
    Adrenal_OnGameFrame();
}

//on reload 重新加载玩家事件
public Event_Reload (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0)
        return;

    SoH_OnReload(iCid); // 急速上弹
}

//on weapon fire 开枪事件
public Event_WeaponFire (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0)
        return;

    decl String:stWpn[24];
    GetEventString(event,"weapon",stWpn,24);

    Pyro_OnWeaponFire(iCid,stWpn);
    DT_OnWeaponFire(iCid,stWpn);
    // 黄金战甲
    if (g_iSur2[iCid]!=2)
    {
        return;
    }
    if (g_iSur2_enable==0
        || g_iSpirit_enable==0        &&    g_iL4D_GameMode==0
        || g_iSpirit_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iSpirit_enable_vs==0    &&    g_iL4D_GameMode==2)
        return;
    // 是否倒地
    if (IsClientInGame(iCid)==true
        && GetClientTeam(iCid)==2
        && GetEntData(iCid,g_iIncapO) != 0 )
    {
        if (g_iSur2[iCid]==2) // 学习黄金战甲
        {
            if (g_iConfirm[iCid]==1 // 玩家身份确认
                && g_iSur2[iCid]==2// 当前玩家选择黄金战甲技能
                && g_iMyDisabler[iCid] == -1 // 是否可自由行动
                && g_iSpiritCooldown[iCid]==0// 黄金战甲CD
                && IsClientInGame(iCid)==true// 游戏中存在此玩家
                && IsPlayerAlive(iCid)==true// 玩家未死亡
                && GetClientTeam(iCid)==2)// 玩家为幸存者
            {
                //----DEBUG----
                //PrintToChatAll("\x03-reviving \x01%i",iCid[iI]);
                //retrieve revive count 获取自起次数
                new iRevCount_ret = GetEntData(iCid, g_iRevCountO, 1);
                // 黑白记录
                new Handle:hPack = CreateDataPack();
                WritePackCell(hPack,iCid);
                WritePackCell(hPack,iRevCount_ret);
                // 通过控制台给予黄金战甲玩家健康值
                new iflags=GetCommandFlags("give");
                SetCommandFlags("give", iflags & ~FCVAR_CHEAT);
                FakeClientCommand(iCid,"give health");
                SetCommandFlags("give", iflags);                
                //                
                //SetEntProp(client, Prop_Data, "m_iMaxHealth", maxhealth);
                //SetEntProp(client, Prop_Data, "m_iHealth", maxhealth);
                // 执行黄金战甲血量修正事件
                CreateTimer(0.5,Spirit_ChangeHP,hPack);
            }
        }
    }

}

//on drug use 药物使用事件
public Action:Event_PillsUsed (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"subject"));
    if (iCid==0) return Plugin_Continue;

    Chem_OnDrugUsed(iCid);

    return Plugin_Continue;
}

//on revive begin 电击开始事件
public Action:Event_ReviveBeginPre (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));

    if (iCid==0) return Plugin_Continue;

    HelpHand_OnReviveBegin (iCid);

    return Plugin_Continue;
}

//on revive end 电击结束事件
public Event_ReviveSuccess (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    new iSub=GetClientOfUserId(GetEventInt(event,"subject"));

    if (iCid==0 || iSub==0) return;

    new iLedge=GetEventInt(event,"ledge_hang");
    //player is labelled as no longer incapped
    g_iPIncap[iSub]=0;

    Unbreakable_OnRevive(iSub, iLedge);
    HelpHand_OnReviveSuccess(iCid,iSub,iLedge);
}

//detects when a person is hanging from a ledge 幸存者挂边事件
public Event_LedgeGrab (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));

    if (iCid==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03spirit ledge grab detected, client: \x01%i",iCid);

    g_iPIncap[iCid]=1; // 倒地
    g_iMyDisabler[iCid] = 0; // 非自由状态
}

public Action:Event_AbilityUsePre (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0 || g_iConfirm[iCid]==0) return Plugin_Continue;
    decl String:stAb[24];
    GetEventString(event,"ability",stAb,24);

    //----DEBUG----
    //PrintToChatAll("\x03ability used: \x01%s", stAb);

    TongueTwister_OnAbilityUse(iCid,stAb);  // 夺命舌速

    return Plugin_Continue;
}

public Event_AbilityUse (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0)
        return;
                
        // 设置CD
		if (IsValidPlayer(iCid) && GetClientTeam(iCid)==3)
		{   
            new iClass = GetEntData(iCid, g_iClassO);
            if (iClass == 1)// 舌头
            {
				//PrintToChatAll("舌头test");
                if (g_iInf3_enable!=0 && g_iTongue_enable!=0 && g_iInf3[iCid]==3 && g_iConfirm[iCid]==1)
                {
					CreateTimer(1.2, Timer_SetSmokeCD, iCid);
                }
            }   
            else if (iClass == 2)// 胖子
            {
				//PrintToChatAll("胖子test");
                if (g_iBlind_enable !=0 && g_iInf1_enable!=0 && g_iInf1[iCid]==2)
                {
					CreateTimer(1.6, Timer_SetBoomerCD, iCid);					
                }
            }
            else if (iClass == 4)// 口水
            {                
                if (g_iInf6_enable!=0 && g_iTwinSF_enable!=0 && g_iInf6[iCid]==1 && g_iConfirm[iCid]==1)
                {					
                    CreateTimer(2.5, Timer_SetSpitterCD, iCid);
                }
            }
		}
		// 是否存在暴走
		if(HasPlayerADTank())
		{
			if(IsValidPlayer(iCid) && GetClientTeam(iCid)==3)
			{
				CreateTimer(1.6, Timer_SetTankCD, iCid);
			}
		}

    decl String:stAb[24];
    GetEventString(event,"ability",stAb,24);

    //----DEBUG----
    //PrintToChatAll("\x03ability used: \x01%s", stAb);

    if (Grass_OnAbilityUse(iCid,stAb)==1)
        return;

    if (Bullet_OnAbilityUse(iCid,stAb)==1)
        return;
}
public Action:Timer_SetSmokeCD(Handle:timer, any:iCid)
{
	//PrintToChatAll("舌头");
    ResetInfectedAbility(iCid, g_flDrag_cdmult);
}
public Action:Timer_SetBoomerCD(Handle:timer, any:iCid)
{
	//PrintToChatAll("胖子");
    ResetInfectedAbility(iCid, g_flBlind_cdmult);
}
public Action:Timer_SetSpitterCD(Handle:timer, any:iCid)
{
	//PrintToChatAll("口水test");
    g_spitter_ability_count[iCid]++;
    if(g_spitter_ability_count[iCid] > 4)
    {
		//PrintToChatAll("口水1111");
        ResetInfectedAbility(iCid, 13.0);// 默认5秒
        g_spitter_ability_count[iCid] = 0;
    }
    else
    {
		//PrintToChatAll("口水tes222t");
        ResetInfectedAbility(iCid, g_flTwinSF_delay);
    }
}
public Action:Timer_SetTankCD(Handle:timer, any:iCid)
{
	//PrintToChatAll("胖子");
    ResetInfectedAbility(iCid, g_flAdrenal_punchcdmult);
}
// 跳事件
public Event_Jump (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0)
        return;

    Frogger_OnJump(iCid);//嗜血之鞍
}
// 被胖子喷中事件
//on becoming slimed, check if player will lose hud
public Event_PlayerNowIt (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"attacker"));
    new iVic=GetClientOfUserId(GetEventInt(event,"userid"));

    if (iAtt==0 || iVic==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03slimed detected, victim/client: \x01%i\x03, attacker: \x01%i",iVic,iAtt);

    //tell plugin another one got slimed (pungent)
    g_iSlimed++;    // 恶魔唤醒喷中幸存人数
    //update plugin var for who vomited last (dead wreckening)
    g_iSlimerLast=iAtt; // 致命袭击

    //check for blind luck      遗忘之咒
    BlindLuck_OnIt(iAtt,iVic);

    //check for barf bagged     恶魔唤醒
    BarfBagged_OnIt(iAtt);

    CreateTimer(15.0,PlayerNoLongerIt,iVic);
}
// 舌头缠住
public Action:Event_TongueGrabPre (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    new iVic = GetClientOfUserId(GetEventInt(event,"victim"));
    if (iCid==0) return Plugin_Continue;

	// 信息显示
	PrintToChat(iVic, "你受到了【%N】舌头的攻击", iCid);
	//CPrintToChatAll("你受到了%N舌头的攻击", iCid);   
    //spirit perk, tell plugin player is disabled by smoker
    g_iMyDisabler[iVic] = iCid; // 不可行动
    //+Inf, tell plugin attacker is disabling
    g_iMyDisableTarget[iCid] = iVic;

    TongueTwister_OnTongueGrab(iCid);   // 夺命舌速
    Drag_OnTongueGrab(iCid);            // 控舌事件
    SmokeIt_OnTongueGrab(iCid, iVic);   // 拖车事件

    return Plugin_Continue;
}
// 舌头释放
public Event_TongueRelease (Handle:event, const String:name[], bool:dontBroadcast)
{
    //+Inf, tell plugin attacker is no longer disabling
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid!=0) g_iMyDisableTarget[iCid] = -1;
    //tell plugin player is free
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));
    if (iVic!=0) g_iMyDisabler[iVic] = -1;

    TongueTwister_OnTongueRelease();// 夺命是否舌速
    SmokeIt_OnTongueRelease(iCid);// 拖车释放事件
	
	new iClass = GetEntData(iCid, g_iClassO);
    if (iClass == 1)// 舌头
    {
		//PrintToChatAll("舌头test");
        if (g_iInf3_enable!=0 && g_iTongue_enable!=0 && g_iInf3[iCid]==3 && g_iConfirm[iCid]==1)
        {
			CreateTimer(0.1, Timer_SetSmokeCD, iCid);					
        }
    }
	// 是否存在暴走
		if(HasPlayerADTank())
		{
			if(IsValidPlayer(iCid) && GetClientTeam(iCid)==3)
			{
				CreateTimer(1.6, Timer_SetTankCD, iCid);
			}
		}
}

public Event_TongueRelease_novictimid (Handle:event, const String:name[], bool:dontBroadcast)
{
    //+Inf, tell plugin attacker is no longer disabling
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid!=0) g_iMyDisableTarget[iCid] = -1;
    //tell plugin player is free
    //new iVic=GetClientOfUserId(GetEventInt(event,"victim"));
    //if (iVic!=0) g_iMyDisabler[iVic] = -1;

    TongueTwister_OnTongueRelease();
    SmokeIt_OnTongueRelease(iCid);
}

public Event_TongueRelease_newsmokerid (Handle:event, const String:name[], bool:dontBroadcast)
{
    //+Inf, tell plugin attacker is no longer disabling
    new iCid=GetClientOfUserId(GetEventInt(event,"smoker"));
    if (iCid!=0) g_iMyDisableTarget[iCid] = -1;
    //tell plugin player is free
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));
    if (iVic!=0) g_iMyDisabler[iVic] = -1;

    TongueTwister_OnTongueRelease();
    SmokeIt_OnTongueRelease(iCid);
}
// hunter撕抓开始
public Event_PounceLanded (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"userid"));
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));

    if (iVic==0 || iAtt==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03pounce land detected, client: \x01%i\x03, victim: \x01%i",iAtt,iVic);

    //spirit victim state is disabled by hunter
    g_iMyDisabler[iVic] = iAtt;
    //+Inf, attacker is disabling someone
    g_iMyDisableTarget[iAtt] = iVic;
}

public Event_PounceStop (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"userid"));
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));

    if (iVic==0 || iAtt==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03pounce stop detected, attacker: \x01%i\x03, victim: \x01%i",iAtt,iVic);

    //victim is no longer disabled
    g_iMyDisabler[iVic] = -1;
    //+Inf, attacker no longer disabling
    g_iMyDisableTarget[iAtt] = -1;
}
// 猴子骑住
public Event_JockeyRide (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"userid"));
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));

    if (iVic==0 || iAtt==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03ride start detected, client: \x01%i\x03, victim: \x01%i",iAtt,iVic);

    //spirit victim state is disabled
    g_iMyDisabler[iVic] = iAtt;
    //+Inf, attacker is disabling someone
    g_iMyDisableTarget[iAtt] = iVic;

    Wind_OnRideStart(iAtt,iVic);
}
// 猴子骑结束
public Event_JockeyRideEnd (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"userid"));
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));

    if (iVic==0 || iAtt==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03ride end detected, attacker: \x01%i\x03, victim: \x01%i",iAtt,iVic);

    //victim is no longer disabled
    g_iMyDisabler[iVic] = -1;
    //+Inf, attacker no longer disabling
    g_iMyDisableTarget[iAtt] = -1;

    Wind_OnRideEnd(iAtt,iVic);

    //since ride like the wind changes the survivor's speeds,
    //reapply extreme conditioning if necessary
    Extreme_Rebuild();
	
	// 是否存在暴走
		if(HasPlayerADTank())
		{
			if(IsValidPlayer(iAtt) && GetClientTeam(iAtt)==3)
			{
				CreateTimer(1.6, Timer_SetTankCD, iAtt);
			}
		}
}
// 牛右键拳头攻击开始
public Event_ChargerPummelStart (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"userid"));
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));

    if (iVic==0 || iAtt==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03ride start detected, client: \x01%i\x03, victim: \x01%i",iAtt,iVic);

    //spirit victim state is disabled
    g_iMyDisabler[iVic] = iAtt;
    //+Inf, attacker is disabling someone
    g_iMyDisableTarget[iAtt] = iVic;
}
// 牛右键拳头攻击结束
public Event_ChargerPummelEnd (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"userid"));
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));

    if (iVic==0 || iAtt==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03ride end detected, attacker: \x01%i\x03, victim: \x01%i",iAtt,iVic);

    //victim is no longer disabled
    g_iMyDisabler[iVic] = -1;
    //+Inf, attacker no longer disabling
    g_iMyDisableTarget[iAtt] = -1;
}
// 牛顶撞开始
public Event_ChargerImpact (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iAtt=GetClientOfUserId(GetEventInt(event,"userid"));
    new iVic=GetClientOfUserId(GetEventInt(event,"victim"));

    if (iVic==0 || iAtt==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03charger impact detected, attacker: \x01%i\x03, victim: \x01%i",iAtt,iVic);

    Scatter_OnImpact(iAtt,iVic);
	PrintToChatAll("【 %N 】使用牛攻击【 %N 】__tst!", iAtt, iVic);
}
// 牛顶撞结束
public Event_ChargerChargeEnd (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));

    if (iCid==0) return;

    //----DEBUG----
    //PrintToChatAll("\x03charger charge end detected, attacker: \x01%i\x03, victim: \x01%i",iAtt,iVic);

    SetEntDataFloat(iCid, g_iLaggedMovementO, 1.0, true);
	
	// 是否存在暴走
		if(HasPlayerADTank())
		{
			if(IsValidPlayer(iCid) && GetClientTeam(iCid)==3)
			{
				CreateTimer(1.6, Timer_SetTankCD, iCid);
			}
		}
}

//** a very important event! =P 重要，幸存初始化
public Event_PlayerSpawn (Handle:event, const String:name[], bool:dontBroadcast)
{
    //stop if game hasn't finished loading
    if (g_bIsLoading == true
        || g_bIsRoundStart == true)
        return;

    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));

    //show the perk menu if their perks are unconfirmed 技能未确认则显示技能菜单
    if (IsClientInGame(iCid)==true
        && IsFakeClient(iCid)==false
        && g_iConfirm[iCid]==0)
        CreateTimer(3.0,Timer_ShowTopMenu,iCid);

    SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0 ,true);
    TwinSF_ResetShotCount(iCid);

    //check survivors for max health 设置最大血量
    //they probably don't have any confirmed perks
    //since they just spawned, so set max to 100
    if (GetClientTeam(iCid)==2)
    {
        if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > 100 )
            SetEntProp(iCid,Prop_Data,"m_iHealth", 100 );

        //set survivor bot's perks 设置机器人技能随机
        if (IsFakeClient(iCid)==true)
        {
            g_iConfirm[iCid]=1;
            g_iSur1[iCid] = Bot_Sur1_PickRandom();
            g_iSur2[iCid] = Bot_Sur2_PickRandom();
            g_iSur3[iCid] = Bot_Sur3_PickRandom();

            //----DEBUG----
            //PrintToChatAll("\x03survivor bot 1: \x01%i\x03, 2:\x01%i",g_iSur1[iCid],g_iSur2[iCid]);
        }

        //----DEBUG----
        //PrintToChatAll("\x03spawned survivor \x01%i\x03 health \x01%i", iCid, GetEntProp(iCid,Prop_Data,"m_iHealth") );

        return;
    }

    new iClass = GetEntData(iCid, g_iClassO);

    //check for smoker first
    if (iClass == 1)
    {
        //----DEBUG----
        //PrintToChatAll("\x03smoker model detected");

        //run a max health check before
        //doing anything else
        new iMaxHP = GetConVarInt(FindConVar("z_gas_health"));
        if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > iMaxHP)
            SetEntProp(iCid,Prop_Data,"m_iHealth", iMaxHP );

        //set bot perks
        if (IsFakeClient(iCid)==true)
        {
            g_iInf3[iCid] = Bot_Inf3_PickRandom();
            g_iConfirm[iCid]=1;

            //----DEBUG----
            //PrintToChatAll("\x03-smoker bot perk \x01%i",g_iInf3[iCid]);
        }

        //----DEBUG----
        //PrintToChatAll("\x03spawned smoker \x01%i\x03 health \x01%i\x03, maxhp \x01%i", iCid, GetEntProp(iCid,Prop_Data,"m_iHealth"), iMaxHP );

        TongueTwister_OnSpawn(iCid);
        Drag_OnSpawn(iCid);

        return;
    }

    //then check for hunter
    else if (iClass == 3)
    {
        //run a max health check before
        //doing anything else
        new iMaxHP = GetConVarInt(FindConVar("z_hunter_health"));
        if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > iMaxHP)
            SetEntProp(iCid,Prop_Data,"m_iHealth", iMaxHP );

        //set bot perks
        if (IsFakeClient(iCid)==true)
        {
            g_iInf4[iCid] = Bot_Inf4_PickRandom();
            g_iConfirm[iCid]=1;

            //----DEBUG----
            //PrintToChatAll("\x03-hunter bot perk \x01%i",g_iInf4[iCid]);
        }

        //----DEBUG----
        //PrintToChatAll("\x03spawned hunter \x01%i\x03 health \x01%i\x03, maxhp \x01%i", iCid, GetEntProp(iCid,Prop_Data,"m_iHealth"), iMaxHP );

        SpeedDemon_OnSpawn(iCid);

        return;
    }

    //lastly, check for boomer
    else if (iClass == 2)
    {
        //run a max health check before
        //doing anything else
        new iMaxHP = GetConVarInt(FindConVar("z_exploding_health"));
        if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > iMaxHP)
            SetEntProp(iCid,Prop_Data,"m_iHealth", iMaxHP );

        //set bot perks
        if (IsFakeClient(iCid)==true)
        {
            g_iInf1[iCid] = Bot_Inf1_PickRandom();
            g_iConfirm[iCid]=1;

            //----DEBUG----
            //PrintToChatAll("\x03-boomer bot perk \x01%i",g_iInf1[iCid]);
        }

        //----DEBUG----
        //PrintToChatAll("\x03spawned boomer \x01%i\x03 health \x01%i\x03, maxhp \x01%i", iCid, GetEntProp(iCid,Prop_Data,"m_iHealth"), iMaxHP );

        Motion_OnSpawn(iCid);
        BlindLuck_OnSpawn(iCid);

        return;
    }

    //check for spitter
    else if (iClass == 4
        && g_iL4D_12 == 2)
    {
        new iMaxHP = GetConVarInt(FindConVar("z_spitter_health"));
        if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > iMaxHP)
            SetEntProp(iCid,Prop_Data,"m_iHealth", iMaxHP );

        //----DEBUG----
        //PrintToChatAll("\x03spitter spawned");

        //set bot perks
        if (IsFakeClient(iCid)==true)
        {
            g_iInf6[iCid] = Bot_Inf6_PickRandom();
            g_iConfirm[iCid]=1;

            //----DEBUG----
            //PrintToChatAll("\x03-spitter bot perk \x01%i",g_iInf1[iCid]);
        }

        //----DEBUG----
        //PrintToChatAll("\x03spawned spitter \x01%i\x03 health \x01%i\x03, maxhp \x01%i", iCid, GetEntProp(iCid,Prop_Data,"m_iHealth"), iMaxHP );

        TwinSF_OnSpawn(iCid);

        return;
    }

    //check for jockey
    else if (iClass == 5
        && g_iL4D_12 == 2)
    {
        new iMaxHP = GetConVarInt(FindConVar("z_jockey_health"));
        if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > iMaxHP)
            SetEntProp(iCid,Prop_Data,"m_iHealth", iMaxHP );

        //set bot perks
        if (IsFakeClient(iCid)==true)
        {
            g_iInf5[iCid] = Bot_Inf5_PickRandom();
            g_iConfirm[iCid]=1;

            //----DEBUG----
            //PrintToChatAll("\x03-jockey bot perk \x01%i",g_iInf1[iCid]);
        }

        //----DEBUG----
        //PrintToChatAll("\x03spawned jockey \x01%i\x03 health \x01%i\x03, maxhp \x01%i", iCid, GetEntProp(iCid,Prop_Data,"m_iHealth"), iMaxHP );

        Cavalier_OnSpawn(iCid);
        Ghost_OnSpawn(iCid);

        return;
    }

    //check for charger
    else if (iClass == 6
        && g_iL4D_12 == 2)
    {
        new iMaxHP = GetConVarInt(FindConVar("z_charger_health"));
        if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > iMaxHP)
            SetEntProp(iCid,Prop_Data,"m_iHealth", iMaxHP );

        //----DEBUG----
        //PrintToChatAll("\x03charger spawned");

        //set bot perks
        if (IsFakeClient(iCid)==true)
        {
            g_iInf7[iCid] = Bot_Inf7_PickRandom();
            g_iConfirm[iCid]=1;

            //----DEBUG----
            //PrintToChatAll("\x03-charger bot perk \x01%i",g_iInf1[iCid]);
        }

        //----DEBUG----
        //PrintToChatAll("\x03spawned charger \x01%i\x03 health \x01%i\x03, maxhp \x01%i", iCid, GetEntProp(iCid,Prop_Data,"m_iHealth"), iMaxHP );

        Scatter_OnSpawn(iCid);

        return;
    }
}

//if item that was picked up is a grenade type, set carried amount in var 捡起火瓶胆汁等投掷物
public Event_ItemPickup (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0) return;
    if (g_iConfirm[iCid]==0)
        return;

    new String:stWpn[24];
    GetEventString(event,"item",stWpn,24);

    //check for grenadier perk
    Pyro_Pickup(iCid,stWpn);

    //check for pack rat perk
    PR_Pickup(iCid, stWpn);
}

//set default perks for connecting players 链接玩家缺省技能
public Event_PConnect(Handle:event, const String:name[], bool:dontBroadcast)
{
    //stop if game is loading
    if (g_bIsLoading == true
        || g_bIsRoundStart == true)
        return;

    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0) return;

    //if any of the perks are set to 0, set default values
    if (g_iSur1[iCid]==0) // 幸存技能
        g_iSur1[iCid] = g_iSur1_default;
    if (g_iSur2[iCid]==0)
        g_iSur2[iCid] = g_iSur2_default;
    if (g_iSur3[iCid]==0)
        g_iSur3[iCid] = g_iSur3_default;
    if (g_iInf1[iCid]==0) // 特感技能
        g_iInf1[iCid] = g_iInf1_default;
    if (g_iInf2[iCid]==0)
        g_iInf2[iCid] = g_iInf2_default;
    if (g_iInf3[iCid]==0)
        g_iInf3[iCid] = g_iInf3_default;
    if (g_iInf4[iCid]==0)
        g_iInf4[iCid] = g_iInf4_default;
    if (g_iInf5[iCid]==0)
        g_iInf5[iCid] = g_iInf5_default;
    if (g_iInf6[iCid]==0)
        g_iInf6[iCid] = g_iInf6_default;
    if (g_iInf7[iCid]==0)
        g_iInf7[iCid] = g_iInf7_default;
    g_iConfirm[iCid]=0;
    g_iGren[iCid]=0;
    g_iGrenThrow[iCid]=0;
    g_iGrenType[iCid]=0;
    g_iMyDisabler[iCid] = -1;
    g_iMyDisableTarget[iCid] = -1;
    g_iPIncap[iCid]=0;
    g_iSpiritCooldown[iCid]=0;

}

//reset perk values when disconnected 丢失链接重置技能
//closes timer for spirit cooldown 关闭技能冷却CD
//and rebuilds DT registry
public Event_PDisconnect(Handle:event, const String:name[], bool:dontBroadcast)
{
    if (g_bIsLoading == true
        || g_bIsRoundStart == true)
        return;

    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0) return;
    g_iSur1[iCid]=0;// 幸存技能重置为0
    g_iSur2[iCid]=0;
    g_iSur3[iCid]=0;
    g_iInf1[iCid]=0;// 特感技能重置为0
    g_iInf2[iCid]=0;
    g_iInf3[iCid]=0;
    g_iInf4[iCid]=0;
    g_iInf5[iCid]=0;
    g_iInf6[iCid]=0;
    g_iInf7[iCid]=0;
    g_iConfirm[iCid]=0;
    g_iGren[iCid]=0;
    g_iGrenThrow[iCid]=0;
    g_iGrenType[iCid]=0;
    g_iMyDisabler[iCid] = -1;
    g_iMyDisableTarget[iCid] = -1;
    g_iPIncap[iCid]=0;
    g_iSpiritCooldown[iCid]=0;// 黄金战甲CD

    if (g_iSpiritTimer[iCid]!=INVALID_HANDLE)
    {
        KillTimer(g_iSpiritTimer[iCid]);
        g_iSpiritTimer[iCid]=INVALID_HANDLE;
    }
    RebuildAll();
    TwinSF_ResetShotCount(iCid);
}

//call menu on first spawn, otherwise set default values for bots // 首次菜单显示，否则为机器缺省值
public Event_PlayerFirstSpawn (Handle:event, const String:name[], bool:dontBroadcast)
{
    if (g_bIsLoading == true
        || g_bIsRoundStart == true)
        return;

    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0) return;
    if (g_iConfirm[iCid]==0 // 判定为新进游戏者
        && IsFakeClient(iCid)==false)
    {
        CreateTimer(3.0,Timer_ShowTopMenu,iCid);
        PrintHintText(iCid,"%t", "WelcomeMessageHint");
        PrintToChat(iCid,"\x03[技能] %t", "WelcomeMessageChat");
    }
}

//checks to show perks menu on roundstart    // 每轮游戏开始显示技能菜单
//and resets various vars to default    // 将原有设置为缺省值
public Event_RoundStart (Handle:event, const String:name[], bool:dontBroadcast)
{
    //tell plugin to not run this function repeatedly until we're done 只显示一次
    if (g_bIsRoundStart == true)
        return;
    else
        g_bIsRoundStart = true;

    //----DEBUG----
    //PrintToChatAll("\x03round start detected");

    //for l4d1, need to change some offsets
    if (g_iL4D_12 == 1)
    {
        //L4D1, Windows
        g_iNextActO = 888;
        g_iAttackTimerO = 1488;
    }
    else if (g_iL4D_12 == 2)
    {
        //check for Linux or Windows by checking
        //a base offset, NextPrimaryAttack for weapons
        //--------------------------------------------
        //numbers have changed since last valve update
        //usually +4 - next activation timer changed for
        //both windows and linux, attack timer changed
        //only for linux, next primary attack changed
        //for both windows and linux

        if (g_iNextPAttO == 5088)
        {
            //L4D2, Windows
            g_iNextActO = 1068;
            g_iAttackTimerO = 5436;
        }
        else if (g_iNextPAttO == 5104)
        {
            //L4D2, Linux
            g_iNextActO = 1092;
            g_iAttackTimerO = 5448;
        }
    }

    //AutoExecConfig(false , "perkmod");

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        //----DEBUG----
        //PrintToChatAll("\x03-cycle %i",iI);

        //reset vars related to spirit perk 技能CD归零
        g_iPIncap[iI]=0;
        g_iSpiritCooldown[iI]=0;
        //reset var related to pack rat perk
        g_bPRalreadyApplying[iI] = false;
        //reset var related to various hunter/smoker perks
        g_iMyDisabler[iI] = -1;
        g_iMyDisableTarget[iI] = -1;
        g_spitter_ability_count[iI] = 0;

        //reset var pointing to client's spirit timer
        //and close the timer handle
        if (g_iSpiritTimer[iI]!=INVALID_HANDLE)
        {
            KillTimer(g_iSpiritTimer[iI]);
            g_iSpiritTimer[iI]=INVALID_HANDLE;
        }

        TwinSF_ResetShotCount(iI);

        //before we run any functions on players
        //check if the game has any players to prevent
        //stupid error messages cropping up on the server
        if (IsServerProcessing()==false)
            continue;

        //only run these commands if player is in-game
        if (IsClientInGame(iI)==true)
        {
            //reset run speeds for martial artist
            SetEntDataFloat(iI,g_iLaggedMovementO, 1.0 ,true);

            if (IsFakeClient(iI)==true) continue;
            //show the perk menu if their perks are unconfirmed 技能未确定，显示技能菜单
            if (g_iConfirm[iI]==0)
                CreateTimer(3.0,Timer_ShowTopMenu,iI);
            //reset var related to blind luck perk
            //SendConVarValue(iI,hCvar,"0");
            SetEntProp(iI, Prop_Send, "m_iHideHUD", 0);
        }

    }

    decl Handle:hCvar;

    //reset vomit vars 重置胖子参数

    if (g_iInf1_enable==1
        && g_iMotion_enable==1)
    {
        hCvar=FindConVar("z_vomit_fatigue");
        ResetConVar(hCvar,false,false);
        g_flVomitFatigue=GetConVarFloat(hCvar);
    }

    //reset tongue vars 重置舌头参数

    if (g_iInf3_enable==1
        && g_iTongue_enable==1) // 缠绕技能
    {
        hCvar=FindConVar("tongue_victim_max_speed"); // 舌头最大速度
        ResetConVar(hCvar,false,false);
        g_flTongueSpeed=GetConVarFloat(hCvar);

        hCvar=FindConVar("tongue_range"); // 舌头范围
        ResetConVar(hCvar,false,false);
        g_flTongueRange=GetConVarFloat(hCvar);

        hCvar=FindConVar("tongue_fly_speed"); // 舌头飞行速度
        ResetConVar(hCvar,false,false);
        g_flTongueFlySpeed=GetConVarFloat(hCvar);
    }

    if (g_iInf3_enable==1
        && g_iDrag_enable==1) // 可拖控舌
    {
        ResetConVar(FindConVar("tongue_allow_voluntary_release"),false,false);//控舌技能为false

        hCvar=FindConVar("tongue_player_dropping_to_ground_time");
        ResetConVar(hCvar,false,false);
        g_flTongueDropTime=GetConVarFloat(hCvar); // 控舌时间CD
    }

    //reset tank attack intervals 重置tank丢舌头CD
    //and rock throw force

    //finally, clear DT and MA registry
    ClearAll();
    //recalculate DT and stopping power
    //permissions on game frame
    RunChecksAll();
    //reset boomer vars
    g_iSlimed        = 0;
    g_iSlimerLast    = 0;
    //reset tank vars
    g_iTank            = 0;
    g_iTankCount    = 0;

    //detect gamemode and difficulty
    new String:stArg[MAXPLAYERS+1];
    //next, check gamemode
    GetConVarString(FindConVar("mp_gamemode"),stArg,64);
    if (StrContains(stArg,"versus",false)!= -1
        || StrContains(stArg,"scavenge",false)!= -1)
        g_iL4D_GameMode=2;
    else if (StrEqual(stArg,"survival",false)==true)
        g_iL4D_GameMode=1;
    else
        g_iL4D_GameMode=0;

    //start global timer that
    //forces bots to have some perks
    //among other things
    if (g_hTimerPerks != INVALID_HANDLE)
    {
        KillTimer(g_hTimerPerks);
        g_hTimerPerks = INVALID_HANDLE;
    }
    // g_hTimerPerks = CreateTimer(2.0,TimerPerks,0,TIMER_REPEAT); 不在使用此黄金战甲自起

    //finally, tell plugin that loading is over and that roundstart can run again
    g_bIsRoundStart = false;
    g_bIsLoading = false;

    //----DEBUG----
    //PrintToChatAll("\x03-difficulty \x01%i\x03, gamemode \x01%i",g_iL4D_Difficulty,g_iL4D_GameMode);

    //----DEBUG----
    //PrintToChatAll("\x03-end round start routine");
}

//resets some temp vars related to perks 玩家死亡数据重置
public Event_PlayerDeath (Handle:event, const String:name[], bool:dontBroadcast)
{
    if (g_bIsLoading == true
        || g_bIsRoundStart == true)
        return;

    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0) return;
    //reset vars related to spirit perk 重置黄金战甲技能
    g_iMyDisabler[iCid] = -1;
    g_iMyDisableTarget[iCid] = -1;
    g_iPIncap[iCid]=0;
    g_iSpiritCooldown[iCid]=0;
    //and also close the spirit cooldown timer
    //and nullify the var pointing to it
    if (g_iSpiritTimer[iCid]!=INVALID_HANDLE)
    {
        KillTimer(g_iSpiritTimer[iCid]);
        g_iSpiritTimer[iCid]=INVALID_HANDLE;
    }
    TwinSF_ResetShotCount(iCid);

    if (IsClientInGame(iCid)==true
        && IsFakeClient(iCid)==false)
    {
        //reset var related to blind luck perk
        //SendConVarValue(iCid,FindConVar("sv_cheats"),"0");
        SetEntProp(iCid, Prop_Send, "m_iHideHUD", 0);
    }

    //rebuild registries for double tap and martial artist 射速和枪支专家重置
    RebuildAll();

    //reset movement rate from martial artist
    SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0 ,true);


    //Tank Routine  如果玩家是tank，tank数量重置
    //------------
    new iClass = GetEntData(iCid, g_iClassO);

    //----DEBUG----
    //PrintToChatAll("\x03player model: %s",st_class);

    //just because I'm not exactly sure...
    if (iClass == 7 || iClass == 8)
    {
        //if a tank is dead, recount the number of tanks left
        //start from zero...
        g_iTankCount=0;
        //...and count up
        for (new iI=1 ; iI<=MaxClients ; iI++)
        {
            if (IsClientInGame(iI)==true
                && IsPlayerAlive(iI)==true
                && GetClientTeam(iI)==3)
            {
                iClass = GetEntData(iI, g_iClassO);
                if (iClass==7 || iClass==8)
                    g_iTankCount++;

                //----DEBUG----
                //PrintToChatAll("\x03-counting \x01%i",iI);
            }
        }

        //----DEBUG----
        //PrintToChatAll("\x03new g_iTankCount= \x01%i",g_iTankCount);

        //if there are no more double trouble tanks, tell plugin there's no more tanks
        if (g_iTankCount==0)
            g_iTank=0;
        //if for some reason it goes below 0, reset vars
        else if (g_iTankCount<0)
        {
            g_iTankCount=0;
            g_iTank=0;
        }

        //----DEBUG----
        //PrintToChatAll("\x03-end tank death routine");
    }
	if (iClass == 4)// 口水
    {                
        g_spitter_ability_count[iCid] = 0;
    }

    //----DEBUG----
    //PrintToChatAll("\x03end death routine for \x01%i",iCid);
}


//sets confirm to 0 and redisplays perks menu 玩家角色或队伍调整，重置技能菜单
public Event_PlayerTransitioned (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0) return;
    //reset their confirm perks var
    //and show the menu
    g_iConfirm[iCid]=0;
    /*CreateTimer(1.0,Timer_ShowTopMenu,iCid);
    //since we just changed maps
    //reset everything for the spirit cooldown timer
    if (g_iSpiritTimer[iCid]!=INVALID_HANDLE)
    {
        KillTimer(g_iSpiritTimer[iCid]);
        g_iSpiritTimer[iCid]=INVALID_HANDLE;
    }*/
}

public Action:Event_Round_Start(Handle:event, const String:name[], bool:dontBroadcast)
{
    /* Reset flags */
    for (new i = 1; i <= MaxClients; i++)
    {
        if (i > 32) { return; }
        clientlosscount[i] = 0;
    }
}

//resets everyone's confirm values on round end, mainly for survival and campaign 回合结束，重置数据及确认值
public Event_RoundEnd (Handle:event, const String:name[], bool:dontBroadcast)
{
    //----DEBUG----
    //PrintToChatAll("round end detected");

    ClearAll();

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        g_iConfirm[iI]=0;
    }

    if (g_hTimerPerks != INVALID_HANDLE)
    {
        KillTimer(g_hTimerPerks);
        g_hTimerPerks = INVALID_HANDLE;
    }

    //tells plugin we're about to start loading
    g_bIsLoading = true;
}

public OnMapStart()
{
    /* Reset flags */
    for (new i = 1; i <= MaxClients; i++)
    {
        if (i > 32) { return; }
        clientlosscount[i] = 0;
    }
}

//as round end function above 地图结束，数据重置，重新加载
public OnMapEnd()
{
    //----DEBUG----
    //PrintToChatAll("map end detected");

    ClearAll();

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        g_iConfirm[iI]=0;
    }

    if (g_hTimerPerks != INVALID_HANDLE)
    {
        KillTimer(g_hTimerPerks);
        g_hTimerPerks = INVALID_HANDLE;
    }

    //tells plugin we're about to start loading
    g_bIsLoading = true;
}

//Anything that uses a global timer for periodic
//checks is also called here; current functions called here:
//Sur1: Spirit 黄金战甲
public Action:TimerPerks (Handle:timer, any:data)
{
    if (IsServerProcessing()==false)
    {
        KillTimer(timer);
        g_hTimerPerks = INVALID_HANDLE;
        return Plugin_Stop;
    }

    Spirit_Timer();

    return Plugin_Continue;
}

//called on a player changing teams     队伍调整事件
//and rebuilds DT registry (and MA as well)
public Event_PlayerTeam (Handle:event, const String:name[], bool:dontBroadcast)
{
    //----DEBUG----
    //PrintToChatAll("\x03change team detected");

    if (g_bIsLoading == true
        || g_bIsRoundStart == true)
        return;

    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0
        || IsValidEntity(iCid)==false
        || IsClientInGame(iCid)==false) return;

    //reset vars related to spirit perk
    g_iPIncap[iCid]=0;
    g_iSpiritCooldown[iCid]=0;
    //reset var related to various hunter/smoker perks
    g_iMyDisabler[iCid] = -1;
    g_iMyDisableTarget[iCid] = -1;

    TwinSF_ResetShotCount(iCid);

    //reset var pointing to client's spirit timer
    //and close the timer handle
    if (g_iSpiritTimer[iCid]!=INVALID_HANDLE)
    {
        KillTimer(g_iSpiritTimer[iCid]);
        g_iSpiritTimer[iCid]=INVALID_HANDLE;
    }

    //reset runspeed
    SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0 ,true);

    //reset blind perk sendprop
    if (IsFakeClient(iCid)==false)
        SetEntProp(iCid, Prop_Send, "m_iHideHUD", 0);

    //rebuild MA and DT registries
    CreateTimer(0.3,Delayed_Rebuild,0);

    //only allow changes of perks if team change was
    //to or from the infected team (implying it's versus)
    if (GetEventInt(event,"team")==3
        || GetEventInt(event,"oldteam")==3)
    {
        g_iConfirm[iCid]=0;
        CreateTimer(1.0,Timer_ShowTopMenu,iCid);
        //apply perks if changing into survivors
        CreateTimer(0.3,Delayed_PerkChecks,iCid);
    }

    //----DEBUG----
    //PrintToChatAll("\x03-end change team routine");
}

//called when plugin is unloaded        // 插件结束事件
//reset all the convars that had permission to run
public OnPluginEnd()
{
    g_bIsRoundStart = true;
    g_bIsLoading = true;

    //----DEBUG----
    //PrintToChatAll("\x03begin pluginend routine");

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        //reset var pointing to client's spirit timer
        //and close the timer handle
        if (g_iSpiritTimer[iI]!=INVALID_HANDLE)
        {
            KillTimer(g_iSpiritTimer[iI]);
            g_iSpiritTimer[iI]=INVALID_HANDLE;
        }

        //before we run any functions on players
        //check if the game has any players to prevent
        //stupid error messages cropping up on the server
        if (IsServerProcessing()==false)
            continue;

        //only run these commands if player is in-game
        if (IsClientInGame(iI)==true)
        {
            //reset run speeds for martial artist
            SetEntDataFloat(iI,g_iLaggedMovementO, 1.0 ,true);

            //reset var related to blind luck perk
            //SendConVarValue(iI,hCvar,"0");
            SetEntProp(iI, Prop_Send, "m_iHideHUD", 0);
        }

    }

    decl Handle:hCvar;

    //reset vomit vars

    if (g_iInf1_enable==1
        && g_iMotion_enable==1)
    {
        hCvar=FindConVar("z_vomit_fatigue");
        ResetConVar(hCvar,false,false);
    }

    //reset tongue vars

    if (g_iInf3_enable==1
        && g_iTongue_enable==1)
    {
        hCvar=FindConVar("tongue_victim_max_speed");
        ResetConVar(hCvar,false,false);

        hCvar=FindConVar("tongue_range");
        ResetConVar(hCvar,false,false);

        hCvar=FindConVar("tongue_fly_speed");
        ResetConVar(hCvar,false,false);
    }

    if (g_iInf3_enable==1
        && g_iDrag_enable==1)
    {
        ResetConVar(FindConVar("tongue_allow_voluntary_release"),false,false);

        hCvar=FindConVar("tongue_player_dropping_to_ground_time");
        ResetConVar(hCvar,false,false);
    }

    //finally, clear DT and MA registry
    ClearAll();

    if (g_hTimerPerks != INVALID_HANDLE)
        KillTimer(g_hTimerPerks);
    g_hTimerPerks = INVALID_HANDLE;

    g_bIsRoundStart = false;
    g_bIsLoading = false;

    //----DEBUG----
    //PrintToChatAll("\x03-end pluginend routine");
}







//=============================
// Misc. Perk Functions   混合技能函数
//=============================

//This is a recently-added function adapted from the complex function I originally wrote
//for body slam. Simpler code I wrote for the other infected-to-survivor perks kept
//inadvertently killing the survivors when they weren't black-and-white... but since
//body slam never had that problem, I decided to use body slam's code to avoid that
//problem altogether... hence this giant function. However, since body slam doesn't fire
//if the original damage exceeds a minimum, it still has its own code.
InfToSurDamageAdd (any:iVic, any:iDmgAdd, any:iDmgOrig) // 特感对幸存伤害增加函数
{
    //don't bother running if client id is zero
    //since sourcemod is intolerant of local servers
    //and if damage add is zero... why bother?
    if (iVic==0 || iDmgAdd<=0) return;

    new iHP=GetEntProp(iVic,Prop_Data,"m_iHealth");

    //CONDITION 1:
    //HEALTH > DMGADD
    //-----------------
    //if health>Min, then run normally
    //easiest condition, since we can
    //apply the damage directly to their hp
    if (iHP>iDmgAdd)
    {
        //----DEBUG----
        //PrintToChatAll("\x03iHP>%i condition",iDmgAdd);

        SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-iDmgAdd );

        //----DEBUG----
        //PrintToChatAll("\x03-%i bonus damage", iDmgAdd );

        return;
    }

    //CONDITION 2:
    //HEALTH <= DMGADD
    //-----------------
    //otherwise, we gotta do a bit of work
    //if survivor's health is
    //less than or equal to 8
    else
    {
        //----DEBUG----
        //PrintToChatAll("\x03iHP<=%i condition",iDmgAdd);
        //PrintToChatAll("\x03-pre-mod iHP: \x01%i",iHP);

        new Float:flHPBuff=GetEntDataFloat(iVic,g_iHPBuffO);

        //CONDITION 2A:
        //HEALTH <= DMGADD
        //&& BUFFER > 0
        //-----------------
        //if victim has health buffer,
        //we need to do some extra work
        //to reduce health buffer as well
        if (flHPBuff>0)
        {
            //----DEBUG----
            //PrintToChatAll("\x03-flHPBuff>0 condition, pre-mod HPbuffer: \x01%f", flHPBuff);

            //since we know the damage add exceeds
            //health, we need to take the difference
            //and apply it to health buffer instead

            //we leave the survivor with 1 health
            //because the engine will take it away
            //when it applies the original damage
            //and we want to avoid strange death behaviour
            //(which occurs if victim's health falls below 0)
            new iDmgCount=iHP-1;
            iDmgAdd-=iDmgCount;
            SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-iDmgCount );

            //and now we take the remainder of the
            //damage add and apply it to the health buffer.

            //if damage add is more than health buffer,
            //set damage add to health buffer amount
            new iHPBuff=RoundToFloor(flHPBuff);
            if (iHPBuff<iDmgAdd) iDmgAdd=iHPBuff;
            //and here we apply the damage to the buffer
            SetEntDataFloat(iVic,g_iHPBuffO, flHPBuff-iDmgAdd ,true);

            //finally, set the proper value in the event info

            //----DEBUG----
            //PrintToChatAll("\x03-damage to health: \x01%i\x03, current health: \x01%i",iDmgCount,GetEntProp(iVic,Prop_Data,"m_iHealth"));
            //PrintToChatAll("\x03-damage to buffer: \x01%i\x03, current buffer: \x01%f",iDmgAdd,GetEntDataFloat(iVic,g_iHPBuffO));

            return;
        }

        //CONDITION 2B:
        //HEALTH <= DMGADD
        //&& BUFFER <= 0
        //-----------------
        //without health buffer, it's straightforward
        //since we just need to apply however much
        //of the damage add we can to the victim's health
        else
        {
            //----DEBUG----
            //PrintToChatAll("\x03no temp hp condition");

            //if original damage exceeds health,
            //just skip the rest since there's no
            //health buffer to worry about,
            //and the engine will incap or kill
            //the survivor anyways with the base damage
            if (iDmgOrig>=iHP) return;

            //to prevent strange death behaviour,
            //reduce damage add to less than that
            //of remaining health if necessary
            if (iDmgAdd>=iHP) iDmgAdd=iHP-1;
            //and if this puts it below 0, just skip everything
            if (iDmgAdd<0) return;

            SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-iDmgAdd );

            //----DEBUG----
            //PrintToChatAll("\x03-%i bonus damage", iDmgAdd );

            return;
        }
    }
}



//just because Sourcemod's RoundToCeil and RoundToFloor functions are 替换sourcemod中两个循环伤害函数
//currently unreliable, I've just written my own workable version
//returns the damage add, randomly picks between higher and lower rounded value
DamageAddRound (iDmgOrig, Float:flDmgMult)
{
    //calculate the damage add
    decl iDmgAdd;
    new Float:flDmg = iDmgOrig * flDmgMult;
    new iDmgRound = RoundToNearest(flDmg);

    //----DEBUG----
    //PrintToChatAll("\x03- fldmg \x01%f\x03 idmground \x01%i",flDmg,iDmgRound);

    //if rounding error can occur...
    new Float:flDmgDiff = iDmgRound - flDmg;
    //check if the rounded value is different from the actual value
    if ( flDmgDiff != 0 )
    {
        //if it is, check if we had rounded up
        if (flDmgDiff > 0)
        {
            //if it was rounded up, then randomize between the upper and lower value
            //and weigh it by each 0.1 amount the rounded value was off by
            if (GetRandomInt(1,10) <= (flDmgDiff * 10) )
                //since we rounded up earlier, just set dmgadd to rounded value
                iDmgAdd = iDmgRound;
            //otherwise, set the damage add to the rounded value minus 1
            else
                iDmgAdd = iDmgRound - 1;
        }
        //the other case is if we rounded down
        else
        {
            //same as above, except multiply it by a negative number to get the abs value
            if (GetRandomInt(1,10) <= (flDmgDiff * (-10)) )
                //since we rounded down earlier, set dmgadd to rounded value plus 1
                iDmgAdd = iDmgRound + 1;
            //otherwise, set the damage add to the rounded value
            else
                iDmgAdd = iDmgRound;
        }
    }

    //if the bonus damage is a clean integer value...
    else
    {
        //just use the value without further fussing
        iDmgAdd = iDmgRound;
    }



    //stop if damage add is <= 0
    if (iDmgAdd <= 0)
        return 0;

    //----DEBUG----
    //PrintToChatAll("\x03- idmgadd \x01%i\x03, idmgorig \x01%i", iDmgAdd, iDmgOrig );

    return iDmgAdd;
}

//on drying from slime, remove hud changes      // 胖子喷吐可视修正
//and lower count of people slimed (pungent)
public Action:PlayerNoLongerIt (Handle:timer, any:iCid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    if (IsClientInGame(iCid)==true
        && IsFakeClient(iCid)==false)
        SetEntProp(iCid, Prop_Send, "m_iHideHUD", 0);
        //SendConVarValue(iCid,FindConVar("sv_cheats"),"0");

    //----DEBUG----
    //PrintToChatAll("\x03client \x01%i\x03 no longer it \n attempting to restore hud",iCid);
    //PrintToChatAll("\x03old g_iSlimed: \x01%i",g_iSlimed);

    if (g_iSlimed>4) g_iSlimed=3;
    else if (g_iSlimed<0) g_iSlimed=0;
    else g_iSlimed--;

    //----DEBUG----
    //PrintToChatAll("\x03new g_iSlimed: \x01%i",g_iSlimed);

    return Plugin_Stop;
}

RunChecksAll ()
{
    if (g_bIsLoading == true
        || g_bIsRoundStart == true)
        return;

    Stopping_RunChecks();
    DT_RunChecks();
    MA_RunChecks();
}

public Action:Delayed_Rebuild (Handle:timer, any:data)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Continue;

    RebuildAll();

    return Plugin_Stop;
}

RebuildAll ()
{
    DT_Rebuild();
    MA_Rebuild();
    Adrenal_Rebuild();
    Extreme_Rebuild();
}

ClearAll ()
{
    DT_Clear();
    MA_Clear();
    Adrenal_Clear();
    Extreme_Rebuild();
}

//delayed perk checks  延迟技能检查
public Action:Delayed_PerkChecks (Handle:timer, any:iCid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    if (IsValidPlayer(iCid)==false
        || GetClientTeam(iCid)!=2)
        return Plugin_Stop;

    Event_Confirm_Unbreakable(iCid);        // 医疗专家确认
    Event_Confirm_Grenadier(iCid);
    Event_Confirm_ChemReliant(iCid); // 枪支专家确认

    return Plugin_Stop;
}

//delayed show menu to prevent weird not-showing on 延缓显示以防止非正常的循环伤害显示
//campaign round restarts...
//... since 1.3, also checks if force random perks server
//setting is on; if so, then assigns perks instead
public Action:Timer_ShowTopMenu (Handle:timer, any:iCid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false
        || IsClientInGame(iCid)==false
        || IsFakeClient(iCid)==true
        || g_bIsLoading == true)
        return Plugin_Stop;

    if (GetConVarInt(g_hMenuAutoShow_enable)==0)
        return Plugin_Stop;

    //----DEBUG----
    //PrintToChatAll("\x03showing menu to \x01%i",iCid);

    if (g_iForceRandom==0)
    {
        new iT = GetClientTeam(iCid);
        if (iT==2)
            SendPanelToClient(Menu_Initial(iCid),iCid,Menu_ChooseInit,MENU_TIME_FOREVER);
        else if (iT==3)
            SendPanelToClient(Menu_Initial(iCid),iCid,Menu_ChooseInit_Inf,MENU_TIME_FOREVER);
    }
    else
        AssignRandomPerks(iCid);

    return Plugin_Stop;
}

//assigns random perks  分配随机技能
AssignRandomPerks (iCid)
{
    //don't do anything if
    //the client id is whacked
    //or if confirm perks is set
    if (iCid > MaxClients
        || iCid <= 0
        || g_iConfirm[iCid]==1)
        return;

    //we track which perks are randomizable
    //in this array
    new iPerkType[10];
    //and keep track of which perk we're on
    decl iPerkCount;


    //SUR1 PERK
    //---------
    iPerkCount=0;

    //1 stopping power
    if (g_iStopping_enable==1            &&    g_iL4D_GameMode==0
        || g_iStopping_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iStopping_enable_vs==1        &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //2 double tap
    if (g_iDT_enable==1            &&    g_iL4D_GameMode==0
        || g_iDT_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iDT_enable_vs==1    &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }    

    //3 sleight of hand
    if (g_iSoH_enable==1        &&    g_iL4D_GameMode==0
        || g_iSoH_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iSoH_enable_vs==1    &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //4 pyrotechnician
    if (g_iPyro_enable==1            &&    g_iL4D_GameMode==0
        || g_iPyro_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iPyro_enable_vs==1        &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //5 martial artist
    if (g_iMA_enable==1            &&    g_iL4D_GameMode==0
        || g_iMA_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iMA_enable_vs==1    &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=5;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iSur1[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];


    //SUR2 PERK
    //---------
    iPerkCount=0;

    //1 unbreakable
    if (g_iUnbreak_enable==1        &&    g_iL4D_GameMode==0
        || g_iUnbreak_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iUnbreak_enable_vs==1    &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //2 spirit
    if (g_iSpirit_enable==1            &&    g_iL4D_GameMode==0
        || g_iSpirit_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iSpirit_enable_vs==1    &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //3 helping hand
    if (g_iHelpHand_enable==1            &&    g_iL4D_GameMode==0
        || g_iHelpHand_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iHelpHand_enable_vs==1        &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iSur2[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];


    //SUR3 PERK
    //------------------

    iPerkCount=0;

    //1 pack rat
    if (g_iPack_enable==1            &&    g_iL4D_GameMode==0
        || g_iPack_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iPack_enable_vs==1        &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //2 chem reliant
    if (g_iChem_enable==1            &&    g_iL4D_GameMode==0
        || g_iChem_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iChem_enable_vs==1        &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //3 hard to kill
    if (g_iHard_enable==1            &&    g_iL4D_GameMode==0
        || g_iHard_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iHard_enable_vs==1        &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //4 extreme conditioning
    if (g_iExtreme_enable==1        &&    g_iL4D_GameMode==0
        || g_iExtreme_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iExtreme_enable_vs==1    &&    g_iL4D_GameMode==2)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iSur3[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];


    //INF1 (BOOMER) PERK
    //------------------
    iPerkCount=0;

    //1 barf bagged
    if (g_iBarf_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //2 blind luck
    if (g_iBlind_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //3 dead wreckening
    if (g_iDead_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //4 motion sickness
    if (g_iMotion_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iInf1[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];


    //INF3 (SMOKER) PERK
    //------------------
    iPerkCount=0;

    //1 tongue twister
    if (g_iTongue_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //2 squeezer
    if (g_iSqueezer_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //3 drag and drop
    if (g_iDrag_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iInf3[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];


    //INF4 (HUNTER) PERK
    //------------------
    iPerkCount=0;

    //1 body slam
    if (g_iBody_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //2 efficient killer
    if (g_iEfficient_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //3 grasshopper
    if (g_iGrass_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //4 speed demon
    if (g_iSpeedDemon_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iInf4[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];



    //INF5 (JOCKEY) PERK
    //------------------
    iPerkCount=0;

    //1 wind
    if (g_iWind_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //2 cavalier
    if (g_iCavalier_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //3 frogger
    if (g_iFrogger_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //4 ghost
    if (g_iGhost_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iInf5[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];



    //INF6 (SPITTER) PERK
    //------------------
    iPerkCount=0;

    //1 twin spitfire
    if (g_iTwinSF_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //1 mega adhesive
    if (g_iMegaAd_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iInf6[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];



    //INF7 (CHARGER) PERK
    //------------------
    iPerkCount=0;

    //1 scatter
    if (g_iScatter_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //1 bullet
    if (g_iBullet_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iInf7[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];



    //INF2 (TANK) PERK
    //----------------
    iPerkCount=0;

    //1 adrenal glands
    if (g_iAdrenal_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //2 Juggernaut
    if (g_iJuggernaut_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //3 metabolic boost
    if (g_iMetabolic_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //4 stormcaller
    if (g_iStorm_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //5 double the trouble
    if (g_iDouble_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=5;
    }

    //randomize a perk
    if (iPerkCount>0)
        g_iInf2[iCid] = iPerkType[ GetRandomInt(1,iPerkCount) ];


    //finally, confirm perks
    //and run the necessary functions
    //as if the player had confirmed
    //their perks through the traditional way
    g_iConfirm[iCid]=1;
    Event_Confirm_Unbreakable(iCid);
    Event_Confirm_Grenadier(iCid);
    Event_Confirm_ChemReliant(iCid);
    Event_Confirm_DT(iCid);
    Event_Confirm_MA(iCid);
    Extreme_Rebuild();

    //lastly, show a panel to the player
    //showing what perks they were given
    SendPanelToClient(Menu_ShowChoices(iCid),iCid,Menu_DoNothing,15);

}

//picks a random perk for bots 为机器选择随机技能
Bot_Sur1_PickRandom ()
{
    //stop if sur1 perks are disabled
    if (g_iSur1_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Sur1 != INVALID_HANDLE)
        GetConVarString(g_hBot_Sur1,stPerk,24);
    else
        stPerk = "1,2,3";

    //stopping power
    if (StrContains(stPerk,"1",false) != -1
        && g_iStopping_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //sleight of hand
    if (StrContains(stPerk,"2",false) != -1
        && g_iSpirit_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //martial artist
    if (StrContains(stPerk,"3",false) != -1
        && g_iUnbreak_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=5;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}

Bot_Sur2_PickRandom ()
{
    //stop if sur2 perks are disabled
    if (g_iSur2_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Sur2 != INVALID_HANDLE)
        GetConVarString(g_hBot_Sur2,stPerk,24);
    else
        stPerk = "1,2,3";

    //unbreakable
    if (StrContains(stPerk,"1",false) != -1
        && g_iChem_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //spirit
    if (StrContains(stPerk,"2",false) != -1
        && g_iHelpHand_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //helping hand
    if (StrContains(stPerk,"3",false) != -1
        && g_iPack_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}

Bot_Sur3_PickRandom ()
{
    //stop if sur2 perks are disabled
    if (g_iSur3_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Sur3 != INVALID_HANDLE)
        GetConVarString(g_hBot_Sur3,stPerk,24);
    else
        stPerk = "1,2";

    //pack rat
    if (StrContains(stPerk,"1",false) != -1
        && g_iChem_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //hard to kill
    if (StrContains(stPerk,"2",false) != -1
        && g_iPack_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}

Bot_Inf1_PickRandom ()
{
    //stop if boomer perks are disabled
    if (g_iInf1_enable==0)
        return 0;

    //----DEBUG----
    //PrintToChatAll("\x03begin random perk for boomer");

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Inf1 != INVALID_HANDLE)
        GetConVarString(g_hBot_Inf1,stPerk,24);
    else
        stPerk = "1,2,3";

    //----DEBUG----
    //PrintToChatAll("\x03-stPerk: \x01%s",stPerk);

    //barf bagged
    if (StrContains(stPerk,"1",false) != -1
        && g_iBarf_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;

        //----DEBUG----
        //PrintToChatAll("\x03-count \x01%i\x03, type \x01%i",iPerkCount,iPerkType[iPerkCount]);
    }

    //blind luck
    if (StrContains(stPerk,"2",false) != -1
        && g_iBlind_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;

        //----DEBUG----
        //PrintToChatAll("\x03-count \x01%i\x03, type \x01%i",iPerkCount,iPerkType[iPerkCount]);
    }

    //dead wreckening
    if (StrContains(stPerk,"3",false) != -1
        && g_iDead_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;

        //----DEBUG----
        //PrintToChatAll("\x03-count \x01%i\x03, type \x01%i",iPerkCount,iPerkType[iPerkCount]);
    }

    //randomize
    decl iReturn;
    if (iPerkCount>0)
        iReturn = iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        iReturn = 0;

    //----DEBUG----
    //PrintToChatAll("\x03-returning \x01%i",iReturn);

    return iReturn;
}

Bot_Inf2_PickRandom ()
{
    //stop if tank perks are disabled
    if (g_iInf2_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Inf2 != INVALID_HANDLE)
        GetConVarString(g_hBot_Inf2,stPerk,24);
    else
        stPerk = "1,2,3,4,5";

    //adrenal glands
    if (StrContains(stPerk,"1",false) != -1
        && g_iAdrenal_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //juggernaut
    if (StrContains(stPerk,"2",false) != -1
        && g_iJuggernaut_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //metabolic boost
    if (StrContains(stPerk,"3",false) != -1
        && g_iMetabolic_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //storm caller
    if (StrContains(stPerk,"4",false) != -1
        && g_iStorm_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //double trouble
    if (StrContains(stPerk,"5",false) != -1
        && g_iDouble_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=5;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}

Bot_Inf3_PickRandom ()
{
    //stop if smoker perks are disabled
    if (g_iInf3_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Inf3 != INVALID_HANDLE)
        GetConVarString(g_hBot_Inf3,stPerk,24);
    else
        stPerk = "1,2,3";

    //tongue twister
    if (StrContains(stPerk,"1",false) != -1
        && g_iTongue_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //squeezer
    if (StrContains(stPerk,"2",false) != -1
        && g_iSqueezer_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //drag and drop
    if (StrContains(stPerk,"3",false) != -1
        && g_iDrag_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}

Bot_Inf4_PickRandom ()
{
    //stop if hunter perks are disabled
    if (g_iInf4_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Inf4 != INVALID_HANDLE)
        GetConVarString(g_hBot_Inf4,stPerk,24);
    else
        stPerk = "1,2";

    //efficient killer
    if (StrContains(stPerk,"1",false) != -1
        && g_iEfficient_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //speed demon
    if (StrContains(stPerk,"2",false) != -1
        && g_iSpeedDemon_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}

Bot_Inf5_PickRandom ()
{
    //stop if jockey perks are disabled
    if (g_iInf5_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Inf5 != INVALID_HANDLE)
        GetConVarString(g_hBot_Inf5,stPerk,24);
    else
        stPerk = "1,2,3,4";

    //ride like the wind
    if (StrContains(stPerk,"1",false) != -1
        && g_iWind_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //cavalier
    if (StrContains(stPerk,"2",false) != -1
        && g_iCavalier_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //frogger
    if (StrContains(stPerk,"3",false) != -1
        && g_iFrogger_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=3;
    }

    //ghost
    if (StrContains(stPerk,"4",false) != -1
        && g_iGhost_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=4;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}

Bot_Inf6_PickRandom ()
{
    //stop if spitter perks are disabled
    if (g_iInf6_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Inf6 != INVALID_HANDLE)
        GetConVarString(g_hBot_Inf6,stPerk,24);
    else
        stPerk = "1";

    //twin spitfire
    if (StrContains(stPerk,"1",false) != -1
        && g_iTwinSF_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //mega adhesive
    if (StrContains(stPerk,"2",false) != -1
        && g_iMegaAd_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}

Bot_Inf7_PickRandom ()
{
    //stop if charger perks are disabled
    if (g_iInf7_enable==0)
        return 0;

    new iPerkType[12];
    new iPerkCount=0;

    decl String:stPerk[24];
    if (g_hBot_Inf7 != INVALID_HANDLE)
        GetConVarString(g_hBot_Inf7,stPerk,24);
    else
        stPerk = "1,2";

    //scattering ram
    if (StrContains(stPerk,"1",false) != -1
        && g_iScatter_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=1;
    }

    //scattering ram
    if (StrContains(stPerk,"2",false) != -1
        && g_iBullet_enable==1)
    {
        iPerkCount++;
        iPerkType[iPerkCount]=2;
    }

    //randomize
    if (iPerkCount>0)
        return iPerkType[ GetRandomInt(1,iPerkCount) ];
    else
        return 0;
}





//=============================
// Sur1: Stopping Power  枪神技能源码
//=============================

//pre-calculates whether stopping power should
//run, since damage events can occur pretty often
Stopping_RunChecks ()
{
    if (g_iSur1_enable==1
        && (g_iStopping_enable==1        &&    g_iL4D_GameMode==0
        || g_iStopping_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iStopping_enable_vs==1        &&    g_iL4D_GameMode==2))
        g_iStopping_meta_enable=1;
    else
        g_iStopping_meta_enable=0;
}

//main damage add function
Stopping_DamageAdd (iAtt, iVic, iTA, iDmgOrig, String:stWpn[])
{
    //check if perk is disabled
    if (g_iStopping_meta_enable==0)
        return 1;

    if (iTA==2
        && g_iSur1[iAtt]==1
        && g_iConfirm[iAtt]==1
        && GetClientTeam(iVic)!=2)
    {
        if (StrEqual(stWpn,"melee",false)==true)
        {
            //----DEBUG----
            //PrintToChatAll("\x03melee weapon detected, not firing");

            return 1;
        }

        //----DEBUG----
        //PrintToChatAll("\x03Pre-mod bullet damage: \x01%i", GetEventInt(event,"dmg_health"));

        new iDmgAdd= RoundToNearest(iDmgOrig * g_flStopping_dmgmult);
        new iHP=GetEntProp(iVic,Prop_Data,"m_iHealth");
        //to prevent strange death behaviour,
        //only deal the full damage add if health > damage add
        if (iHP>iDmgAdd)
        {
            SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-iDmgAdd );
        }
        //if health < damage add, only deal health-1 damage
        else
        {
            iDmgAdd=iHP-1;
            //don't bother if the modified damage add
            //ends up being an insignificant amount
            if (iDmgAdd<0)
                return 1;
            SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-iDmgAdd );
        }

        //----DEBUG----
        //PrintToChatAll("\x03Post-mod bullet damage: \x01%i",GetEventInt(event,"dmg_health"));

        return 1;
    }

    return 0;
}

//against common infected
public Event_InfectedHurtPre (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"attacker"));

    if (iCid==0 || g_iConfirm[iCid]==0)
        return;

    //check if perk is disabled
    if (g_iStopping_meta_enable==0)
        return;

    //----DEBUG----
    //PrintToChatAll("\x03infected hurt, iAtt: %i, iEntid: %i, i_odmg: %i, iHP: %i",iAtt,iEntid,i_odmg,GetEntProp(iEntid,Prop_Data,"m_iHealth"));

    if (g_iSur1[iCid]==1 // 枪神
        && GetClientTeam(iCid)==2)
    {
        new iEntid=GetEventInt(event,"entityid");
        new i_odmg=GetEventInt(event,"amount");
        new i_dmga=RoundToNearest(i_odmg * g_flStopping_dmgmult);

        //----DEBUG----
        //PrintToChatAll("\x03Pre-mod damage: \x01%i, \x03pre-mod health: \x01%i", GetEventInt(event,"amount"),GetEntProp(iEntid,Prop_Data,"m_iHealth"));

        SetEntProp(iEntid,Prop_Data,"m_iHealth", GetEntProp(iEntid,Prop_Data,"m_iHealth")-i_dmga );
        //******SetEventInt(event,"dmg_health", i_odmg+i_dmga );

        //----DEBUG----
        //PrintToChatAll("\x03Post-mod damage: \x01%i, \x03post-mod health: \x01%i",GetEventInt(event,"amount"),GetEntProp(iEntid,Prop_Data,"m_iHealth"));
    }
}


//=============================
// Sur1: Double Tap 夺命射速技能源码
//=============================

//called on round starts and on convar changes
//does the checks to determine whether DT
//should be run every game frame
DT_RunChecks ()
{
    if (g_iSur1_enable==1
        && (g_iDT_enable==1        &&    g_iL4D_GameMode==0
        || g_iDT_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iDT_enable_vs==1    &&    g_iL4D_GameMode==2))
        g_iDT_meta_enable=1;
    else
        g_iDT_meta_enable=0;
}

//called on confirming perks
//simply adds player to registry of DT users
Event_Confirm_DT (iCid)
{
    if (g_iDTRegisterCount<0)
        g_iDTRegisterCount=0;
    if (IsClientInGame(iCid)==true
        && IsPlayerAlive(iCid)==true
        && g_iSur1[iCid]==2
        && g_iConfirm[iCid]==1
        && GetClientTeam(iCid)==2)
    {
        g_iDTRegisterCount++;
        g_iDTRegisterIndex[g_iDTRegisterCount]=iCid;

        //----DEBUG----
        //PrintToChatAll("\x03double tap on confirm, registering \x01%i",iCid);
    }    
}

//called whenever the registry needs to be rebuilt
//to cull any players who have left or died, etc.
//(called on: player death, player disconnect,
//closet rescue, change teams)
DT_Rebuild ()
{
    //clears all DT-related vars
    DT_Clear();

    //if the server's not running or
    //is in the middle of loading, stop
    if (IsServerProcessing()==false)
        return;

    //----DEBUG----
    //PrintToChatAll("\x03double tap rebuilding registry");

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        if (IsClientInGame(iI)==true
            && IsPlayerAlive(iI)==true
            && g_iSur1[iI]==2
            && g_iConfirm[iI]==1
            && GetClientTeam(iI)==2)
        {
            g_iDTRegisterCount++;
            g_iDTRegisterIndex[g_iDTRegisterCount]=iI;

            //----DEBUG----
            //PrintToChatAll("\x03-registering \x01%i",iI);
        }
    }
}

//called to clear out DT registry
//(called on: round start, round end, map end)
DT_Clear ()
{
    g_iDTRegisterCount=0;
    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        g_iDTRegisterIndex[iI]= -1;
        g_iDTEntid[iI] = -1;
        g_flDTNextTime[iI]= -1.0;
    }
}

//this is the big momma!
//since this is called EVERY game frame,
//we need to be careful not to run too many functions
//kinda hard, though, considering how many things
//we have to check for =.=
DT_OnGameFrame()
{
    //or if no one has DT, don't bother either
    if (g_iDTRegisterCount==0)
        return;

    //stop if perk is disabled
    if (g_iDT_meta_enable==0)
        return;

    //this tracks the player's id, just to
    //make life less painful...
    decl iCid;
    //this tracks the player's gun id
    //since we adjust numbers on the gun,
    //not the player
    decl iEntid;
    //this tracks the calculated next attack
    decl Float:flNextTime_calc;
    //this, on the other hand, tracks the current next attack
    decl Float:flNextTime_ret;
    //and this tracks next melee attack times
    decl Float:flNextTime2_ret;
    //and this tracks the game time
    new Float:flGameTime=GetGameTime();

    //theoretically, to get on the DT registry
    //all the necessary checks would have already
    //been run, so we don't bother with any checks here
    for (new iI=1; iI<=g_iDTRegisterCount; iI++)
    {
        //PRE-CHECKS: RETRIEVE VARS
        //-------------------------

        iCid = g_iDTRegisterIndex[iI];
        //stop on this client
        //when the next client id is null
        if (iCid <= 0
            || IsValidEntity(iCid)==false)
            return;
        //skip this client if they're disabled
        //if (g_iMyDisabler[iCid] != -1) continue;

        //we have to adjust numbers on the gun, not the player
        //so we get the active weapon id here
        iEntid = GetEntDataEnt2(iCid,g_iActiveWO);
        //if the retrieved gun id is -1, then...
        //wtf mate? just move on
        if (iEntid == -1
            || IsValidEntity(iEntid)==false)
            continue;

        //----DEBUG----
        /*
        new iNextAttO=    FindSendPropInfo("CTerrorPlayer","m_flNextAttack");
        new iIdleTimeO=    FindSendPropInfo("CTerrorGun","m_flTimeWeaponIdle");
        PrintToChatAll("\x03DT, NextAttack \x01%i %f\x03, TimeIdle \x01%i %f",
            iNextAttO,
            GetEntDataFloat(iCid,iNextAttO),
            iIdleTimeO,
            GetEntDataFloat(iEntid,iIdleTimeO)
            );
        */


        //PRE-CHECK 1
        //-----------
        new iEntid_stored = g_iDTEntid[iCid];
        new Float:flNextTime_stored = g_flDTNextTime[iCid];
        //and here is the retrieved next attack time
        flNextTime_ret = GetEntDataFloat(iEntid,g_iNextPAttO);


        //CHECK 1: BEFORE ADJUSTED SHOT IS MADE
        //------------------------------------
        //since this will probably be the case most of
        //the time, we run this first
        //checks: gun is unchanged; time of shot has not passed
        //actions: nothing
        if (iEntid_stored == iEntid
            && flNextTime_stored >= flNextTime_ret)
        {
            //----DEBUG----
            //PrintToChatAll("\x03DT client \x01%i\x03; before shot made",iCid );

            continue;
        }


        //PRE-CHECK 2
        //-----------
        //and for retrieved next melee time
        flNextTime2_ret = GetEntDataFloat(iEntid,g_iNextSAttO);

        //CHECK 2: INFER IF MELEEING
        //--------------------------
        //since we don't want to shorten the interval
        //incurred after swinging, we try to guess when
        //a melee attack is made
        //checks: if melee attack time > engine time
        //actions: nothign
        if (flNextTime2_ret > flGameTime)
        {
            //----DEBUG----
            //PrintToChatAll("\x03DT client \x01%i\x03; melee attack inferred",iCid );

            g_flDTNextTime[iCid]=flNextTime_ret;

            continue;
        }


        //CHECK 3: AFTER ADJUSTED SHOT IS MADE
        //------------------------------------
        //at this point, either a gun was swapped, or
        //the attack time needs to be adjusted
        //checks: stored gun id same as retrieved gun id,
        // and retrieved next attack time is after stored value
        if (iEntid_stored == iEntid
            && flNextTime_stored < flNextTime_ret)
        {
            //----DEBUG----
            //PrintToChatAll("\x03DT after adjusted shot\n-pre, client \x01%i\x03; entid \x01%i\x03; enginetime\x01 %f\x03; NextTime_orig \x01 %f\x03; interval \x01%f",iCid,iEntid,flGameTime,flNextTime_ret, flNextTime_ret-flGameTime );

            //this is a calculation of when the next primary attack
            //will be after applying double tap values
            flNextTime_calc = ( flNextTime_ret - flGameTime ) * g_flDT_rate + flGameTime;

            //then we store the value
            g_flDTNextTime[iCid] = flNextTime_calc;

            //and finally adjust the value in the gun
            SetEntDataFloat(iEntid, g_iNextPAttO, flNextTime_calc, true);

            //----DEBUG----
            //PrintToChatAll("\x03-post, NextTime_calc \x01 %f\x03; new interval \x01%f",GetEntDataFloat(iEntid,g_iNextPAttO), GetEntDataFloat(iEntid,g_iNextPAttO)-flGameTime );

            continue;
        }


        //CHECK 4: ON WEAPON SWITCH
        //-------------------------
        //at this point, the only reason DT hasn't fired
        //should be that the weapon had switched
        //checks: retrieved gun id doesn't match stored id
        // or stored id is null
        //actions: updates stored gun id
        // and sets stored next attack time to retrieved value
        if (iEntid_stored != iEntid)
        {
            //----DEBUG----
            //PrintToChatAll("\x03DT client \x01%i\x03; weapon switch inferred",iCid );

            //now we update the stored vars
            g_iDTEntid[iCid]=iEntid;
            g_flDTNextTime[iCid]=flNextTime_ret;
            continue;
        }

        //----DEBUG----
        //PrintToChatAll("\x03DT client \x01%i\x03; reached end of checklist...",iCid );
    }
}

//the % chance for a bullet to be a freebie 夺命射速
DT_OnWeaponFire (iCid, String:stWpn[])
{
    //or if no one has DT, don't bother either
    if (g_iDTRegisterCount==0)
        return;

    //stop if perk is disabled
    if (g_iDT_meta_enable==0)
        return;

    //run the basic perkmod checks
    if (IsClientInGame(iCid)==false
        || IsPlayerAlive(iCid)==false
        || g_iSur1[iCid]!=2
        || g_iConfirm[iCid]!=1
        || GetClientTeam(iCid)!=2)
    {
        return;
    }

    //stop if gun fired is a prop (minigun/m2hb)
    if (strncmp(stWpn, "prop", 4, false) == 0)
    {
        //----DEBUG----
        //PrintToChatAll("\x03- prop detected, stWpn = \x01%s", stWpn);

        return;
    }

    //this tracks the player's gun id
    //since we adjust numbers on the gun, 纠正枪数据，而非玩家设计
    //not the player
    //we have to adjust numbers on the gun, not the player
    //so we get the active weapon id here
    new iEntid = GetEntDataEnt2(iCid,g_iActiveWO);
    //if the retrieved gun id is -1, then...
    //wtf mate? just move on
    if (iEntid == -1
        || IsValidEntity(iEntid)==false)
        return;

    //----DEBUG----
    //PrintToChatAll("\x03DT conservation fired, stWpn = \x01%s\x03, iEntid \x01%i\x03, g_iDTEntid_firearm = \x01%i", stWpn, iEntid, g_iDTEntid_firearm[iCid]);



    //CHECK 1: WEAPON FIRED IS A FIREARM
    //----------------------------------
    //checks if weapon is a firearm by checking
    //if the entid stored in the global var is
    //the same as the entid of the fired weapon 是否有效枪支
    //if it isn't, then the fired weapon entid
    //will be checked in the next section
    //also the string check is to make sure this
    //doesn't fire when a player uses a minigun
    if (iEntid == g_iDTEntid_firearm[iCid])
    {
        //----DEBUG----
        //PrintToChatAll("\x03- shots fired");

        DT_BulletConservation(iEntid);
    }

    //at this point, we need to check the weapon
    //type and confirm it's a firearm at all - if
    //the player just switched weapons, then we
    //just update the global var with the new entid
    else
    {
        //make sure it's actually a firearm - 
        //all firearms' class ids have "pistol",
        //"rifle", "shotgun", "sniper" or "smg"
        if (StrContains(stWpn, "pistol", false)!= -1 // 手枪
            || StrContains(stWpn, "smg", false)!= -1 // 冲锋
            || StrContains(stWpn, "rifle", false)!= -1 // 步枪
            || StrContains(stWpn, "shotgun", false)!= -1// 霰弹
            || StrContains(stWpn, "sniper", false)!= -1    )// 狙击
        {
            //----DEBUG----
            //PrintToChatAll("\x03- firearm detected, updating global var");

            //update the global var to make processing
            //future shots faster
            g_iDTEntid_firearm[iCid] = iEntid;
            //and then run a chance for this bullet - would
            //suck if the first bullet was never conserved
            DT_BulletConservation(iEntid);
        }
        else
        {
            //----DEBUG----
            //PrintToChatAll("\x03- non firearm detected");

            //if it's not a firearm, then update global var
            //(doesn't really do anything)
            g_iDTEntid_firearm[iCid] = -1;
        }
    }
}

//function for bullet conservation - runs the chance and changes the gun parameters
DT_BulletConservation (iEntid)
{
    if (GetRandomInt(1,100) <= g_flDT_freechance * 100)
    {
        new iClip = GetEntData(iEntid, g_iClipO);
        SetEntData(iEntid, g_iClipO, iClip + 1 );

        //----DEBUG----
        //PrintToChatAll("\x03DT bullet conserved");
    }
}



//=============================
// Sur1: Sleight of Hand    急速上弹技能源码
//=============================

//on the start of a reload
SoH_OnReload (iCid)
{
    //check if perk is disabled
    if (g_iSur1_enable==0
        || g_iSoH_enable==0        &&    g_iL4D_GameMode==0
        || g_iSoH_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iSoH_enable_vs==0    &&    g_iL4D_GameMode==2)
        return;

    if (g_iSur1[iCid]==3
        && g_iConfirm[iCid]==1
        && GetClientTeam(iCid)==2)
    {
        //----DEBUG----
        //PrintToChatAll("\x03SoH client \x01%i\x03; start of reload detected",iCid );

        new iEntid = GetEntDataEnt2(iCid,g_iActiveWO);
        if (IsValidEntity(iEntid)==false) return;

        decl String:stClass[32];
        GetEntityNetClass(iEntid,stClass,32);

        //----DEBUG----
        //PrintToChatAll("\x03-class of gun: \x01%s",stClass );

        //for non-shotguns
        if (StrContains(stClass,"shotgun",false) == -1)
        {
            SoH_MagStart(iEntid,iCid);
            return;
        }

        //shotguns are a bit trickier since the game
        //tracks per shell inserted - and there's TWO
        //different shotguns with different values =.=
        else if (StrContains(stClass,"autoshotgun",false) != -1)
        {
            //crate a pack to send clientid and gunid through to the timer
            new Handle:hPack = CreateDataPack();
            WritePackCell(hPack, iCid);
            WritePackCell(hPack, iEntid);

            CreateTimer(0.1,SoH_AutoshotgunStart,hPack);
            return;
        }

        else if (StrContains(stClass,"shotgun_spas",false) != -1)
        {
            //crate a pack to send clientid and gunid through to the timer
            new Handle:hPack = CreateDataPack();
            WritePackCell(hPack, iCid);
            WritePackCell(hPack, iEntid);

            CreateTimer(0.1,SoH_SpasShotgunStart,hPack);
            return;
        }

        else if (StrContains(stClass,"pumpshotgun",false) != -1
            || StrContains(stClass,"shotgun_chrome",false) != -1)
        {
            //crate a pack to send clientid and gunid through to the timer
            new Handle:hPack = CreateDataPack();
            WritePackCell(hPack, iCid);
            WritePackCell(hPack, iEntid);

            CreateTimer(0.1,SoH_PumpshotgunStart,hPack);
            return;
        }
    }
}

//called for mag loaders
SoH_MagStart (iEntid, iCid)
{
    //----DEBUG----
    //PrintToChatAll("\x05-magazine loader detected,\x03 gametime \x01%f", GetGameTime());

    new Float:flGameTime = GetGameTime();
    new Float:flNextTime_ret = GetEntDataFloat(iEntid,g_iNextPAttO);

    //----DEBUG----
    /*PrintToChatAll("\x03- pre, gametime \x01%f\x03, retrieved nextattack\x01 %i %f\x03, retrieved time idle \x01%i %f",
        flGameTime,
        g_iNextAttO,
        GetEntDataFloat(iCid,g_iNextAttO),
        g_iTimeIdleO,
        GetEntDataFloat(iEntid,g_iTimeIdleO)
        );*/

    //this is a calculation of when the next primary attack
    //will be after applying sleight of hand values
    //NOTE: at this point, only calculate the interval itself,
    //without the actual game engine time factored in
    new Float:flNextTime_calc = ( flNextTime_ret - flGameTime ) * g_flSoH_rate ;

    //we change the playback rate of the gun
    //just so the player can "see" the gun reloading faster
    SetEntDataFloat(iEntid, g_iPlayRateO, 1.0/g_flSoH_rate, true);

    //create a timer to reset the playrate after
    //time equal to the modified attack interval
    CreateTimer( flNextTime_calc, SoH_MagEnd, iEntid);

    //experiment to remove double-playback bug
    new Handle:hPack = CreateDataPack();
    WritePackCell(hPack, iCid);
    //this calculates the equivalent time for the reload to end
    //if the survivor didn't have the SoH perk
    new Float:flStartTime_calc = flGameTime - ( flNextTime_ret - flGameTime ) * ( 1 - g_flSoH_rate ) ;
    WritePackFloat(hPack, flStartTime_calc);
    //now we create the timer that will prevent the annoying double playback
    if ( (flNextTime_calc - 0.4) > 0 )
        CreateTimer( flNextTime_calc - 0.4 , SoH_MagEnd2, hPack);

    //and finally we set the end reload time into the gun
    //so the player can actually shoot with it at the end
    flNextTime_calc += flGameTime;
    SetEntDataFloat(iEntid, g_iTimeIdleO, flNextTime_calc, true);
    SetEntDataFloat(iEntid, g_iNextPAttO, flNextTime_calc, true);
    SetEntDataFloat(iCid, g_iNextAttO, flNextTime_calc, true);

    //----DEBUG----
    /*PrintToChatAll("\x03- post, calculated nextattack \x01%f\x03, gametime \x01%f\x03, retrieved nextattack\x01 %i %f\x03, retrieved time idle \x01%i %f",
        flNextTime_calc,
        flGameTime,
        g_iNextAttO,
        GetEntDataFloat(iCid,g_iNextAttO),
        g_iTimeIdleO,
        GetEntDataFloat(iEntid,g_iTimeIdleO)
        );*/
}

//called for autoshotguns
public Action:SoH_AutoshotgunStart (Handle:timer, Handle:hPack)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    ResetPack(hPack);
    new iCid = ReadPackCell(hPack);
    new iEntid = ReadPackCell(hPack);
    CloseHandle(hPack);
    hPack = CreateDataPack();
    WritePackCell(hPack, iCid);
    WritePackCell(hPack, iEntid);

    if (iCid <= 0
        || iEntid <= 0
        || IsValidEntity(iCid)==false
        || IsValidEntity(iEntid)==false
        || IsClientInGame(iCid)==false)
        return Plugin_Stop;

    //----DEBUG----
    /*PrintToChatAll("\x03-autoshotgun detected, iEntid \x01%i\x03, startO \x01%i\x03, insertO \x01%i\x03, endO \x01%i",
        iEntid,
        g_iShotStartDurO,
        g_iShotInsertDurO,
        g_iShotEndDurO
        );
    PrintToChatAll("\x03- pre mod, start \x01%f\x03, insert \x01%f\x03, end \x01%f",
        g_flSoHAutoS,
        g_flSoHAutoI,
        g_flSoHAutoE
        );*/
                
    //then we set the new times in the gun
    SetEntDataFloat(iEntid,    g_iShotStartDurO,    g_flSoHAutoS*g_flSoH_rate,    true);
    SetEntDataFloat(iEntid,    g_iShotInsertDurO,    g_flSoHAutoI*g_flSoH_rate,    true);
    SetEntDataFloat(iEntid,    g_iShotEndDurO,        g_flSoHAutoE*g_flSoH_rate,    true);

    //we change the playback rate of the gun
    //just so the player can "see" the gun reloading faster
    SetEntDataFloat(iEntid, g_iPlayRateO, 1.0/g_flSoH_rate, true);

    //and then call a timer to periodically check whether the
    //gun is still reloading or not to reset the animation
    //but first check the reload state; if it's 2, then it
    //needs a pump/cock before it can shoot again, and thus
    //needs more time
    if (g_iL4D_12 == 2)
        CreateTimer(0.3,SoH_ShotgunEnd,hPack,TIMER_REPEAT);
    else if (g_iL4D_12 == 1)
    {
        if (GetEntData(iEntid,g_iShotRelStateO)==2)
            CreateTimer(0.3,SoH_ShotgunEndCock,hPack,TIMER_REPEAT);
        else
            CreateTimer(0.3,SoH_ShotgunEnd,hPack,TIMER_REPEAT);
    }

    //----DEBUG----
    /*PrintToChatAll("\x03- after mod, start \x01%f\x03, insert \x01%f\x03, end \x01%f",
        g_flSoHAutoS,
        g_flSoHAutoI,
        g_flSoHAutoE
        );*/

    return Plugin_Stop;
}

public Action:SoH_SpasShotgunStart (Handle:timer, Handle:hPack)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    ResetPack(hPack);
    new iCid = ReadPackCell(hPack);
    new iEntid = ReadPackCell(hPack);
    CloseHandle(hPack);
    hPack = CreateDataPack();
    WritePackCell(hPack, iCid);
    WritePackCell(hPack, iEntid);

    if (iCid <= 0
        || iEntid <= 0
        || IsValidEntity(iCid)==false
        || IsValidEntity(iEntid)==false
        || IsClientInGame(iCid)==false)
        return Plugin_Stop;

    //----DEBUG----
    /*PrintToChatAll("\x03-autoshotgun detected, iEntid \x01%i\x03, startO \x01%i\x03, insertO \x01%i\x03, endO \x01%i",
        iEntid,
        g_iShotStartDurO,
        g_iShotInsertDurO,
        g_iShotEndDurO
        );
    PrintToChatAll("\x03- pre mod, start \x01%f\x03, insert \x01%f\x03, end \x01%f",
        g_flSoHSpasS,
        g_flSoHSpasI,
        g_flSoHSpasE
        );*/
                
    //then we set the new times in the gun
    SetEntDataFloat(iEntid,    g_iShotStartDurO,    g_flSoHSpasS*g_flSoH_rate,    true);
    SetEntDataFloat(iEntid,    g_iShotInsertDurO,    g_flSoHSpasI*g_flSoH_rate,    true);
    SetEntDataFloat(iEntid,    g_iShotEndDurO,        g_flSoHSpasE*g_flSoH_rate,    true);

    //we change the playback rate of the gun
    //just so the player can "see" the gun reloading faster
    SetEntDataFloat(iEntid, g_iPlayRateO, 1.0/g_flSoH_rate, true);

    //and then call a timer to periodically check whether the
    //gun is still reloading or not to reset the animation
    //but first check the reload state; if it's 2, then it
    //needs a pump/cock before it can shoot again, and thus
    //needs more time
    CreateTimer(0.3,SoH_ShotgunEnd,hPack,TIMER_REPEAT);

    //----DEBUG----
    /*PrintToChatAll("\x03- after mod, start \x01%f\x03, insert \x01%f\x03, end \x01%f",
        g_flSoHSpasS,
        g_flSoHSpasI,
        g_flSoHSpasE
        );*/

    return Plugin_Stop;
}

//called for pump shotguns
public Action:SoH_PumpshotgunStart (Handle:timer, Handle:hPack)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    ResetPack(hPack);
    new iCid = ReadPackCell(hPack);
    new iEntid = ReadPackCell(hPack);
    CloseHandle(hPack);
    hPack = CreateDataPack();
    WritePackCell(hPack, iCid);
    WritePackCell(hPack, iEntid);

    if (iCid <= 0
        || iEntid <= 0
        || IsValidEntity(iCid)==false
        || IsValidEntity(iEntid)==false
        || IsClientInGame(iCid)==false)
        return Plugin_Stop;

    //----DEBUG----
    /*PrintToChatAll("\x03-pumpshotgun detected, iEntid \x01%i\x03, startO \x01%i\x03, insertO \x01%i\x03, endO \x01%i",
        iEntid,
        g_iShotStartDurO,
        g_iShotInsertDurO,
        g_iShotEndDurO
        );
    PrintToChatAll("\x03- pre mod, start \x01%f\x03, insert \x01%f\x03, end \x01%f",
        g_flSoHPumpS,
        g_flSoHPumpI,
        g_flSoHPumpE
        );*/

    //then we set the new times in the gun
    SetEntDataFloat(iEntid,    g_iShotStartDurO,    g_flSoHPumpS*g_flSoH_rate,    true);
    SetEntDataFloat(iEntid,    g_iShotInsertDurO,    g_flSoHPumpI*g_flSoH_rate,    true);
    SetEntDataFloat(iEntid,    g_iShotEndDurO,        g_flSoHPumpE*g_flSoH_rate,    true);

    //we change the playback rate of the gun
    //just so the player can "see" the gun reloading faster
    SetEntDataFloat(iEntid, g_iPlayRateO, 1.0/g_flSoH_rate, true);

    //and then call a timer to periodically check whether the
    //gun is still reloading or not to reset the animation
    if (g_iL4D_12 == 2)
        CreateTimer(0.3,SoH_ShotgunEnd,hPack,TIMER_REPEAT);
    else if (g_iL4D_12 == 1)
    {
        if (GetEntData(iEntid,g_iShotRelStateO)==2)
            CreateTimer(0.3,SoH_ShotgunEndCock,hPack,TIMER_REPEAT);
        else
            CreateTimer(0.3,SoH_ShotgunEnd,hPack,TIMER_REPEAT);
    }

    //----DEBUG----
    /*PrintToChatAll("\x03- after mod, start \x01%f\x03, insert \x01%f\x03, end \x01%f",
        g_flSoHPumpS,
        g_flSoHPumpI,
        g_flSoHPumpE
        );*/

    return Plugin_Stop;
}

//this resets the playback rate on non-shotguns
public Action:SoH_MagEnd (Handle:timer, any:iEntid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    //----DEBUG----
    //PrintToChatAll("\x03SoH reset playback, magazine loader");

    if (iEntid <= 0
        || IsValidEntity(iEntid)==false)
        return Plugin_Stop;

    SetEntDataFloat(iEntid, g_iPlayRateO, 1.0, true);

    return Plugin_Stop;
}

public Action:SoH_MagEnd2 (Handle:timer, Handle:hPack)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
    {
        CloseHandle(hPack);
        return Plugin_Stop;
    }

    //----DEBUG----
    //PrintToChatAll("\x03SoH reset playback, magazine loader");

    ResetPack(hPack);
    new iCid = ReadPackCell(hPack);
    new Float:flStartTime_calc = ReadPackFloat(hPack);
    CloseHandle(hPack);

    if (iCid <= 0
        || IsValidEntity(iCid)==false
        || IsClientInGame(iCid)==false)
        return Plugin_Stop;

    //experimental, remove annoying double-playback
    new iVMid = GetEntDataEnt2(iCid,g_iViewModelO);
    SetEntDataFloat(iVMid, g_iVMStartTimeO, flStartTime_calc, true);

    //----DEBUG----
    //PrintToChatAll("\x03- end SoH mag loader, icid \x01%i\x03 starttime \x01%f\x03 gametime \x01%f", iCid, flStartTime_calc, GetGameTime());

    return Plugin_Stop;
}

public Action:SoH_ShotgunEnd (Handle:timer, Handle:hPack)
{
    //----DEBUG----
    //PrintToChatAll("\x03-autoshotgun tick");

    ResetPack(hPack);
    new iCid = ReadPackCell(hPack);
    new iEntid = ReadPackCell(hPack);

    if (IsServerProcessing()==false
        || iCid <= 0
        || iEntid <= 0
        || IsValidEntity(iCid)==false
        || IsValidEntity(iEntid)==false
        || IsClientInGame(iCid)==false)
    {
        KillTimer(timer);
        return Plugin_Stop;
    }

    if (GetEntData(iEntid,g_iShotRelStateO)==0)
    {
        //----DEBUG----
        //PrintToChatAll("\x03-shotgun end reload detected");

        SetEntDataFloat(iEntid, g_iPlayRateO, 1.0, true);

        //new iCid=GetEntPropEnt(iEntid,Prop_Data,"m_hOwner");
        new Float:flTime=GetGameTime()+0.2;
        SetEntDataFloat(iCid,    g_iNextAttO,    flTime,    true);
        SetEntDataFloat(iEntid,    g_iTimeIdleO,    flTime,    true);
        SetEntDataFloat(iEntid,    g_iNextPAttO,    flTime,    true);

        KillTimer(timer);
        CloseHandle(hPack);
        return Plugin_Stop;
    }

    return Plugin_Continue;
}

//since cocking requires more time, this function does
//exactly as the above, except it adds slightly more time
public Action:SoH_ShotgunEndCock (Handle:timer, any:hPack)
{
    //----DEBUG----
    //PrintToChatAll("\x03-autoshotgun tick");

    ResetPack(hPack);
    new iCid = ReadPackCell(hPack);
    new iEntid = ReadPackCell(hPack);

    if (IsServerProcessing()==false
        || iCid <= 0
        || iEntid <= 0
        || IsValidEntity(iCid)==false
        || IsValidEntity(iEntid)==false
        || IsClientInGame(iCid)==false)
    {
        KillTimer(timer);
        return Plugin_Stop;
    }

    if (GetEntData(iEntid,g_iShotRelStateO)==0)
    {
        //----DEBUG----
        //PrintToChatAll("\x03-shotgun end reload + cock detected");

        SetEntDataFloat(iEntid, g_iPlayRateO, 1.0, true);

        //new iCid=GetEntPropEnt(iEntid,Prop_Data,"m_hOwner");
        new Float:flTime= GetGameTime() + 1.0;
        SetEntDataFloat(iCid,    g_iNextAttO,    flTime,    true);
        SetEntDataFloat(iEntid,    g_iTimeIdleO,    flTime,    true);
        SetEntDataFloat(iEntid,    g_iNextPAttO,    flTime,    true);

        KillTimer(timer);
        CloseHandle(hPack);
        return Plugin_Stop;
    }

    return Plugin_Continue;
}



//=============================
// Sur1: Pyrotechnician     爆破专家技能源码
//=============================

//on pickup
Pyro_Pickup(iCid, String:stWpn[])
{
    if (g_iSur1[iCid]==4
        && g_iSur1_enable==1
        && (g_iPyro_enable==1        &&    g_iL4D_GameMode==0
        || g_iPyro_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iPyro_enable_vs==1        &&    g_iL4D_GameMode==2))
    {
        //only bother with checks if they aren't throwing
        if (g_iGrenThrow[iCid]==0)
        {
            //check if the weapon is a grenade type
            if (StrEqual(stWpn,"pipe_bomb",false)==true
                || StrEqual(stWpn,"molotov",false)==true
                || StrEqual(stWpn,"vomitjar",false)==true)
            {
                decl String:stWpn2[24];
                if (stWpn[0]=='p')
                    stWpn2="土制";
                else if (stWpn[0]=='v' && g_iL4D_12 == 2)
                    stWpn2="胆汁";
                else
                    stWpn2="火瓶";
                //if so, then check if either 0 or 2 are being carried
                //if true, then act normally and give player 2 grenades
                if (g_iGren[iCid]==0
                    || g_iGren[iCid]==2)
                {
                
                    new mod1 = GetRandomInt(0, 2);    //增加一个随机获得数量
                    switch(mod1)
                    {
                    case 0:
                        {
                            g_iGren[iCid]=12;            //爆破专家物品数量修改
                            PrintHintText(iCid,"爆破专家: %t %i %s(s)", "GrenadierCarryHint", g_iGren[iCid], stWpn2);
                        }
                    case 1:
                        {
                            g_iGren[iCid]=14;            //爆破专家物品数量修改
                            PrintHintText(iCid,"爆破专家: %t %i %s(s)", "GrenadierCarryHint", g_iGren[iCid], stWpn2);    
                        }
                    case 2:
                        {
                            g_iGren[iCid]=16;            //爆破专家物品数量修改
                            PrintHintText(iCid,"爆破专家: %t %i %s(s)", "GrenadierCarryHint", g_iGren[iCid], stWpn2);
                        }
                    }
                    
                }
                //otherwise, only give them one and tell them to
                //throw the grenade before picking up another one;
                //this is to prevent abuses with throwing infinite nades
                else
                {
                    g_iGren[iCid]=1;
                    PrintHintText(iCid,"%t %s! %t", "GrenadierCantTake2Grenades_A", stWpn2, "GrenadierCantTake2Grenades_B");
                }
            }
        }
        //if they are in the middle of throwing, then reset the var
        else if (g_iGrenThrow[iCid]==1)
            g_iGrenThrow[iCid]=0;
    }
}

//called when tossing
Pyro_OnWeaponFire(iCid, String:stWpn[])
{
    //check if perk is enabled
    if (g_iSur1_enable==0
        || g_iPyro_enable==0        &&    g_iL4D_GameMode==0
        || g_iPyro_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iPyro_enable_vs==0        &&    g_iL4D_GameMode==2)
        return;

    if (g_iConfirm[iCid]==0
        || g_iSur1[iCid]!=4) return; // 爆破专家

    //----DEBUG----
    //PrintToChatAll("\x03 weapon fired: \x01%s", st_wpn);

    new bool:bPipe=StrEqual(stWpn,"pipe_bomb",false);
    new bool:bMol=StrEqual(stWpn,"molotov",false);
    new bool:bVomit=StrEqual(stWpn,"vomitjar",false);
    if (bPipe || bMol || bVomit)
    {
        g_iGren[iCid]--;        //reduce count by 1
        decl String:stWpn2[24];

        if (g_iGren[iCid]>0)        //do they still have grenades left?
        {
            if (bPipe==true)
            {
                g_iGrenType[iCid]=1;
                stWpn2="土制";
            }
            else if (bMol==true)
            {
                g_iGrenType[iCid]=2;
                stWpn2="火瓶";
            }
            else
            {
                g_iGrenType[iCid]=3;
                stWpn2="胆汁";
            }

            PrintHintText(iCid,"爆破专家: %t %i %s %t", "GrenadierCounter_A", g_iGren[iCid], stWpn2, "GrenadierCounter_B");
            CreateTimer(2.5,Grenadier_DelayedGive,iCid);
        }
    }
}

//gives the grenade a few seconds later 
//(L4D takes a while to remove the grenade from inventory after it's been thrown)
public Action:Grenadier_DelayedGive (Handle:timer,any:iCid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    if (iCid==0
        || g_iConfirm[iCid]==0
        || g_iSur1[iCid]!=4)
        return Plugin_Continue;

    new iflags=GetCommandFlags("give");
    new String:st_give[24];

    if (g_iGrenType[iCid]==1)
        st_give="give pipe_bomb";
    else if (g_iGrenType[iCid]==2)
        st_give="give molotov";
    else
        st_give="give vomitjar";

    g_iGrenType[iCid]=0;
    g_iGrenThrow[iCid]=1;    //client now considered to be "in the middle of throwing"
    SetCommandFlags("give", iflags & ~FCVAR_CHEAT);
    FakeClientCommand(iCid,st_give);
    SetCommandFlags("give", iflags);

    return Plugin_Stop;
}

//called on roundstarts or on confirming perks
//gives a random grenade to the player
Event_Confirm_Grenadier (iCid)
{
    if (iCid==0
        || GetClientTeam(iCid)!=2
        || IsPlayerAlive(iCid)==false
        || g_iConfirm[iCid]==0
        || g_iSur1[iCid]!=4)
        return;

    //check if perk is enabled
    if (g_iSur1_enable==0
        || g_iPyro_enable==0        &&    g_iL4D_GameMode==0
        || g_iPyro_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iPyro_enable_vs==0        &&    g_iL4D_GameMode==2)
        return;

    //reset grenade count on player
    g_iGren[iCid]=0;

    new iflags=GetCommandFlags("give");
    new String:st_give[24];

    decl iMax;
    if (g_iL4D_12 == 2)
        iMax = 2;
    else if (g_iL4D_12 == 1)
        iMax = 1;

    new iI=GetRandomInt(0,iMax);
    if (iI==0)
        st_give="give pipe_bomb";
    else if (iI==1)
        st_give="give molotov";
    else if (iI==2)
        st_give="give vomitjar";

    SetCommandFlags("give", iflags & ~FCVAR_CHEAT);
    FakeClientCommand(iCid,st_give);
    SetCommandFlags("give", iflags);
    return;
}



//=============================
// Sur1: Martial Artist     近战专家技能源码
//=============================

MA_RunChecks ()
{
    if (g_iSur1_enable==1
        || (g_iMA_enable==1        &&    g_iL4D_GameMode==0)
        || (g_iMA_enable_sur==1    &&    g_iL4D_GameMode==1)
        || (g_iMA_enable_vs==1    &&    g_iL4D_GameMode==2))
        g_iMA_meta_enable=1;
    else
        g_iMA_meta_enable=0;
}

//called on confirming perks
//adds player to registry of MA users
//and sets movement speed
Event_Confirm_MA (iCid, iWeapon=0)
{
    if (g_iMARegisterCount<0)
        g_iMARegisterCount=0;

    //check if perk is enabled
    if (g_iSur1_enable==0
        || g_iMA_enable==0        &&    g_iL4D_GameMode==0
        || g_iMA_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iMA_enable_vs==0    &&    g_iL4D_GameMode==2)
        return;

    if (IsClientInGame(iCid)==true
        && IsPlayerAlive(iCid)==true
        && g_iSur1[iCid]==5
        && g_iConfirm[iCid]==1
        && GetClientTeam(iCid)==2)
    {
        g_iMARegisterCount++;
        g_iMARegisterIndex[g_iMARegisterCount]=iCid;

        //give player the weapon of choice
        //0 = nothing
        //1 = baseball bat
        //2 = cricket bat
        //3 = crowbar
        //4 = electric guitar
        //5 = fire axe
        //6 = frying pan
        //7 = katana
        //8 = machete
        //9 = tonfa
        decl String:stS[MAXPLAYERS+1];
        switch (iWeapon)
        {
        case 1:
            stS="give baseball_bat";
        }

        //----DEBUG----
        //PrintToChatAll("\x03martial artist on confirm, registering \x01%i",iCid);
    }    
}

//called whenever the registry needs to be rebuilt
//to cull any players who have left or died, etc.
//resets survivor's speeds and reassigns speed boost
//(called on: player death, player disconnect,
//closet rescue, change teams, convar change)
MA_Rebuild ()
{
    //clears all DT-related vars
    MA_Clear();

    //if the server's not running or
    //is in the middle of loading, stop
    if (IsServerProcessing()==false)
        return;

    //check if perk is enabled
    if (g_iSur1_enable==0
        || g_iMA_enable==0        &&    g_iL4D_GameMode==0
        || g_iMA_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iMA_enable_vs==0    &&    g_iL4D_GameMode==2)
        return;

    //----DEBUG----
    //PrintToChatAll("\x03martial artist rebuilding registry");

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        if (IsClientInGame(iI)==true
            && IsPlayerAlive(iI)==true
            && g_iSur1[iI]==5
            && g_iConfirm[iI]==1
            && GetClientTeam(iI)==2)
        {
            g_iMARegisterCount++;
            g_iMARegisterIndex[g_iMARegisterCount]=iI;

            //----DEBUG----
            //PrintToChatAll("\x03-registering \x01%i",iI);
        }
    }
}

//called to clear out registry
//and reset movement speeds
//(called on: round start, round end, map end)
MA_Clear ()
{
    g_iMARegisterCount=0;
    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        g_iMARegisterIndex[iI]= -1;
    }
}

MA_OnGameFrame()
{
    //stop if MA is disabled in any way
    if (g_iMA_meta_enable==0)
        return 0;

    //or if no one has DT, don't bother either
    if (g_iMARegisterCount==0)
        return 0;

    decl iCid;
    //this tracks the player's ability id
    decl iEntid;
    //this tracks the calculated next attack
    decl Float:flNextTime_calc;
    //this, on the other hand, tracks the current next attack
    decl Float:flNextTime_ret;
    //and this tracks the game time
    new Float:flGameTime=GetGameTime();

    for (new iI=1; iI<=g_iMARegisterCount; iI++)
    {
        //PRE-CHECKS 1: RETRIEVE VARS
        //---------------------------

        iCid = g_iMARegisterIndex[iI];
        //stop on this client
        //when the next client id is null
        if (!IsValidPlayer(iCid)) return 0;
        //skip this client if they're disabled, or, you know, dead
        if (g_iMyDisabler[iCid] != -1) continue;
        if (IsPlayerAlive(iCid)==false) continue;

        //we have to adjust numbers on the gun, not the player
        //so we get the active weapon id here
        iEntid = GetEntDataEnt2(iCid,g_iActiveWO);
        //if the retrieved gun id is -1, then...
        //wtf mate? just move on
        if (iEntid == -1) continue;
        //and here is the retrieved next attack time
        flNextTime_ret = GetEntDataFloat(iEntid,g_iNextPAttO);

        //----DEBUG----
        //PrintToChat(iCid,"\x03shove penalty \x01%i\x03, max penalty \x01%i",GetEntData(iCid,g_iMeleeFatigueO), g_iMA_maxpenalty);

        //PRE-CHECKS 2: MOD SHOVE FATIGUE
        //-------------------------------
        if ( GetEntData(iCid,g_iMeleeFatigueO) > g_iMA_maxpenalty )
        {
            SetEntData(iCid, g_iMeleeFatigueO, g_iMA_maxpenalty);
        }





        //CHECK 1: IS PLAYER USING A KNOWN NON-MELEE WEAPON?
        //--------------------------------------------------
        //as the title states... to conserve processing power,
        //if the player's holding a gun for a prolonged time
        //then we want to be able to track that kind of state
        //and not bother with any checks
        //checks: weapon is non-melee weapon
        //actions: do nothing
        if (iEntid == g_iMAEntid_notmelee[iCid])
        {
            //----DEBUG----
            //PrintToChatAll("\x03MA client \x01%i\x03; non melee weapon, ignoring",iCid );

            g_iMAAttCount[iCid] = 0;
            continue;
        }



        //PRE CHECK 1.5
        //-------------
        new iMAEntid = g_iMAEntid[iCid];
        new iMAAttCount = g_iMAAttCount[iCid];

        //CHECK 1.5: THE PLAYER HASN'T SWUNG HIS WEAPON FOR A WHILE
        //-------------------------------------------------------
        //in this case, if the player made 1 swing of his 2 strikes,
        //and then paused long enough, we should reset his strike count
        //so his next attack will allow him to strike twice
        //checks: is the delay between attacks greater than 0.8s?
        //actions: set attack count to 0, and CONTINUE CHECKS
        if (iMAEntid == iEntid
            && iMAAttCount != 0
            && (flGameTime - flNextTime_ret) > 0.8)
        {
            //----DEBUG----
            //PrintToChatAll("\x03MA client \x01%i\x03; hasn't swung weapon",iCid );

            g_iMAAttCount[iCid] = 0;
        }



        //PRE CHECK 2
        //-----------
        new Float:flMANextTime = g_flMANextTime[iCid];

        //CHECK 2: BEFORE ADJUSTED ATT IS MADE
        //------------------------------------
        //since this will probably be the case most of
        //the time, we run this first
        //checks: weapon is unchanged; time of shot has not passed
        //actions: do nothing
        if (iMAEntid == iEntid
            && flMANextTime >= flNextTime_ret)
        {
            //----DEBUG----
            //PrintToChatAll("\x03DT client \x01%i\x03; before shot made",iCid );

            continue;
        }



        //CHECK 3: AFTER ADJUSTED ATT IS MADE
        //------------------------------------
        //at this point, either a gun was swapped, or
        //the attack time needs to be adjusted
        //checks: stored gun id same as retrieved gun id,
        // and retrieved next attack time is after stored value
        //actions: adjusts next attack time
        if (iMAEntid == iEntid
            && flMANextTime < flNextTime_ret)
        {
            //----DEBUG----
            //PrintToChatAll("\x03DT after adjusted shot\n-pre, client \x01%i\x03; entid \x01%i\x03; enginetime\x01 %f\x03; NextTime_orig \x01 %f\x03; interval \x01%f",iCid,iEntid,flGameTime,flNextTime_ret, flNextTime_ret-flGameTime );
            //new Float:flNextTime_retSA = GetEntDataFloat(iEntid,g_iNextSAttO);
            //PrintToChatAll("\x05DT\x03 enginetime\x01 %f\x03; nextPA \x01%f\x03; PAinterval \x01%f\x03\n nextSA \x01%f\x03 SAinterval \x01%f", flGameTime, flNextTime_ret, flNextTime_ret-flGameTime, flNextTime_retSA, flNextTime_retSA-flGameTime );



            //> CHECK FOR SHOVES/WEAPON DRAWS
            //-------------------------------
            new Float:flInterval = flNextTime_ret-flGameTime;
            if (flInterval > 0.7331
                && flInterval < 0.7335)
            {
                //----DEBUG----
                //PrintToChatAll("\x05DT\x03 shove inferred");

                g_flMANextTime[iCid] = flNextTime_ret;
                continue;
            }
            if (flInterval < 0.534)
            {
                //----DEBUG----
                //PrintToChatAll("\x05DT\x03 weapon draw inferred");

                g_flMANextTime[iCid] = flNextTime_ret;
                continue;
            }


            g_iMAAttCount[iCid]++;
            if (g_iMAAttCount[iCid]>2)
                g_iMAAttCount[iCid]=0;
            iMAAttCount = g_iMAAttCount[iCid];

            //> MOD ATTACK
            //------------
            if (iMAAttCount == 1
                || iMAAttCount == 2)
            {
                //this is a calculation of when the next primary attack
                //will be after applying double tap values
                //flNextTime_calc = ( flNextTime_ret - flGameTime ) * g_flMA_attrate + flGameTime;
                flNextTime_calc = flGameTime + 0.3 ;

                //then we store the value
                g_flMANextTime[iCid] = flNextTime_calc;

                //and finally adjust the value in the gun
                SetEntDataFloat(iEntid, g_iNextPAttO, flNextTime_calc, true);

                //----DEBUG----
                //PrintToChatAll("\x03-post, NextTime_calc \x01 %f\x03; new interval \x01%f",GetEntDataFloat(iEntid,g_iNextPAttO), GetEntDataFloat(iEntid,g_iNextPAttO)-flGameTime );

                continue;
            }

            //> DON'T MOD ATTACK
            //------------------
            if (g_iMAAttCount[iCid]==0)
            {
                g_flMANextTime[iCid] = flNextTime_ret;
                continue;
            }
        }



        //CHECK 4: CHECK THE WEAPON
        //-------------------------
        //lastly, at this point we need to check if we are, in fact,
        //using a melee weapon =P we check if the current weapon is
        //the same one stored in memory; if it is, move on;
        //otherwise, check if it's a melee weapon - if it is,
        //store and continue; else, continue.
        //checks: if the active weapon is a melee weapon
        //actions: store the weapon's entid into either
        // the known-melee or known-non-melee variable

        //----DEBUG----
        //PrintToChatAll("\x03DT client \x01%i\x03; weapon switch inferred",iCid );

        //check if the weapon is a melee
        decl String:stName[32];
        GetEntityNetClass(iEntid,stName,32);
        if (StrEqual(stName,"CTerrorMeleeWeapon",false)==true)
        {
            //if yes, then store in known-melee var
            g_iMAEntid[iCid]=iEntid;
            g_flMANextTime[iCid]=flNextTime_ret;
            continue;
        }
        else
        {
            //if no, then store in known-non-melee var
            g_iMAEntid_notmelee[iCid]=iEntid;
            continue;
        }
    }

    return 0;
}



//=============================
// Sur2: Unbreakable        医疗专家技能源码
//=============================

//on heal; gives 80% of bonus hp
Unbreakable_OnHeal (iCid)
{
    //check if perk is enabled
    if (g_iSur2_enable==0
        || g_iUnbreak_enable==0            &&    g_iL4D_GameMode==0
        || g_iUnbreak_enable_sur==0        &&    g_iL4D_GameMode==1
        || g_iUnbreak_enable_vs==0        &&    g_iL4D_GameMode==2)
        return;

    if (g_iSur2[iCid]==1)
    {
        CreateTimer(0.5,Unbreakable_Delayed_Heal,iCid);
        //SetEntProp(iCid,Prop_Data,"m_iHealth", GetEntProp(iCid,Prop_Data,"m_iHealth")+(g_iUnbreak_hp*8/10) );

        //run a check to see if for whatever reason
        //the player's health is above 200
        //since 200 is the clamped maximum for unbreakable
        //in which case we set their health to 200
        if (GetEntProp(iCid,Prop_Data,"m_iHealth") > 200)
            CreateTimer(0.5,Unbreakable_Delayed_SetHigh,iCid);

        PrintHintText(iCid,"医疗专家: %t!", "UnbreakableHint");
    }
}

//called when player confirms his choices;
//gives 30 hp (to bring hp to 130, assuming survivor
//wasn't stupid and got himself hurt before confirming perks)
Event_Confirm_Unbreakable (iCid)
{
    new iHP=GetEntProp(iCid,Prop_Data,"m_iHealth");
    if (iCid==0 || g_iConfirm[iCid]==0) return;
    new TC=GetClientTeam(iCid);

    //check if perk is enabled
    if (g_iSur2_enable==0
        || g_iUnbreak_enable==0            &&    g_iL4D_GameMode==0
        || g_iUnbreak_enable_sur==0        &&    g_iL4D_GameMode==1
        || g_iUnbreak_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        //if not, check if hp is higher than it should be
        if (iHP>100
            && TC==2)
        {
            //if it IS higher, reduce hp to 100
            //otherwise, no way to know whether previous owner
            //had unbreakable, so give the incoming player
            //the benefit of doubt
            CreateTimer(0.5,Unbreakable_Delayed_SetLow,iCid);
        }
        return;
    }

    //if we've gotten up to this point, the perk is enabled
    if (g_iSur2[iCid]==1
        && TC==2)
    {
        if (iHP>100
            && iHP < (100+g_iUnbreak_hp) )
            CreateTimer(0.5,Unbreakable_Delayed_Max,iCid);
        else if (iHP<=100)
            CreateTimer(0.5,Unbreakable_Delayed_Normal,iCid);
        PrintHintText(iCid,"医疗专家: %t!", "UnbreakableHint");

        //run a check to see if for whatever reason
        //the player's health is above 200
        //since 200 is the clamped maximum for unbreakable
        //in which case we set their health to 200
        if (GetEntProp(iCid,Prop_Data,"m_iHealth") > 200)
            CreateTimer(0.5,Unbreakable_Delayed_SetHigh,iCid);
    }
    //if not, check if hp is higher than it should be
    else if (g_iSur2[iCid]!=1
        && iHP>100
        && TC==2)
    {
        //if it IS higher, reduce hp to 100
        //otherwise, no way to know whether previous owner
        //had unbreakable, so give the incoming player
        //the benefit of doubt
        CreateTimer(0.5,Unbreakable_Delayed_SetLow,iCid);
    }
}

//on rescue; gives 50% of bonus hp
Unbreakable_OnRescue (iCid)
{
    if (g_iSur2[iCid]==1)
    {
        //check if perk is enabled
        if (g_iSur2_enable==0
            || g_iUnbreak_enable==0            &&    g_iL4D_GameMode==0
            || g_iUnbreak_enable_sur==0        &&    g_iL4D_GameMode==1
            || g_iUnbreak_enable_vs==0        &&    g_iL4D_GameMode==2)
            return;

        CreateTimer(0.5,Unbreakable_Delayed_Rescue,iCid);
        PrintHintText(iCid,"医疗专家: %t!", "UnbreakableHint");

        //run a check to see if for whatever reason
        //the player's health is above 200
        //since 200 is the clamped maximum for unbreakable
        //in which case we set their health to 200
        if (GetEntProp(iCid,Prop_Data,"m_iHealth") > 200)
            CreateTimer(0.5,Unbreakable_Delayed_SetHigh,iCid);
    }
}

//on revive; gives 50% of bonus hp in temp hp
Unbreakable_OnRevive (iSub, iLedge)
{
    //check for unbreakable for the subject
    //only fires if they were NOT hanging from a ledge
    if (g_iSur2[iSub]==1
        && g_iConfirm[iSub]==1
        && iLedge == 0)
    {
        //check if perk is enabled
        if (g_iSur1_enable==1
            && (g_iUnbreak_enable==1    &&    g_iL4D_GameMode==0
            || g_iUnbreak_enable_sur==1    &&    g_iL4D_GameMode==1
            || g_iUnbreak_enable_vs==1    &&    g_iL4D_GameMode==2))
        {
            SetEntDataFloat(iSub,g_iHPBuffO, GetEntDataFloat(iSub,g_iHPBuffO)+(g_iUnbreak_hp/2) ,true);
            PrintHintText(iSub,"医疗专家: %t!", "UnbreakableHint");
        }
    }
}

//these timer functions apply health bonuses
//after a delay, hopefully to avoid bugs
public Action:Unbreakable_Delayed_Max (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==true
        && IsValidEntity(iCid)==true
        && IsClientInGame(iCid)==true)
        SetEntProp(iCid,Prop_Data,"m_iHealth", 100+g_iUnbreak_hp );

    KillTimer(timer);
    return Plugin_Stop;
}

public Action:Unbreakable_Delayed_Normal (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==true
        && IsValidEntity(iCid)==true
        && IsClientInGame(iCid)==true)
    {
        SetEntProp(iCid,Prop_Data,"m_iHealth", GetEntProp(iCid,Prop_Data,"m_iHealth")+g_iUnbreak_hp );

        if (GetEntProp(iCid,Prop_Data,"m_iHealth") > (100+g_iUnbreak_hp) )
            SetEntProp(iCid,Prop_Data,"m_iHealth", 100+g_iUnbreak_hp );
    }

    KillTimer(timer);
    return Plugin_Stop;
}

public Action:Unbreakable_Delayed_Heal (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==true
        && IsValidEntity(iCid)==true
        && IsClientInGame(iCid)==true)
    {
        SetEntProp(iCid,Prop_Data,"m_iHealth", GetEntProp(iCid,Prop_Data,"m_iHealth") + (g_iUnbreak_hp*8/10) );

        if (GetEntProp(iCid,Prop_Data,"m_iHealth") > (100+g_iUnbreak_hp) )
            SetEntProp(iCid,Prop_Data,"m_iHealth", 100+g_iUnbreak_hp );
    }

    KillTimer(timer);
    return Plugin_Stop;
}

public Action:Unbreakable_Delayed_Rescue (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==true
        && IsValidEntity(iCid)==true
        && IsClientInGame(iCid)==true)
    {
        SetEntProp(iCid,Prop_Data,"m_iHealth", GetEntProp(iCid,Prop_Data,"m_iHealth") + (g_iUnbreak_hp/2) );

        if (GetEntProp(iCid,Prop_Data,"m_iHealth") > (100+g_iUnbreak_hp) )
            SetEntProp(iCid,Prop_Data,"m_iHealth", 100+g_iUnbreak_hp );
    }

    KillTimer(timer);
    return Plugin_Stop;
}

public Action:Unbreakable_Delayed_SetHigh (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==true
        && IsValidEntity(iCid)==true
        && IsClientInGame(iCid)==true)
        SetEntProp(iCid,Prop_Data,"m_iHealth", 200 );

    KillTimer(timer);
    return Plugin_Stop;
}

public Action:Unbreakable_Delayed_SetLow (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==true
        && IsValidEntity(iCid)==true
        && IsClientInGame(iCid)==true)
        SetEntProp(iCid,Prop_Data,"m_iHealth", 100 );

    KillTimer(timer);
    return Plugin_Stop;
}



//=============================
// Sur2: Spirit     黄金战甲技能源码
//=============================

//called by global timer "TimerPerks"
//periodically runs checks to see if anyone should self-revive
//since sometimes self-revive won't fire if someone's being disabled
//by, say, a hunter
Spirit_Timer ()
{
    //check if perk is enabled
    if (g_iSur2_enable==0        
        || g_iSpirit_enable==0        &&    g_iL4D_GameMode==0
        || g_iSpirit_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iSpirit_enable_vs==0    &&    g_iL4D_GameMode==2)
        return;

    //this var counts how many people are incapped
    //but for the first part, it checks whether anyone has spirit
    new iCount=0;

    //preliminary check; if no one has
    //the spirit perk, this function will return   获取学习黄金战甲技能玩家数
    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        if (g_iSur2[iI]==2)
        {
            iCount++;
            break;
        }
    }
    if (iCount<=0) return;
    else iCount=0;

    //----DEBUG----
    //PrintToChatAll("\x03spirit timer check");

    //this array will hold client ids
    //for the possible candidates for self-revives
    new iCid[18];

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        //fill array with whoever's incapped
        if (IsClientInGame(iI)==true
            && GetClientTeam(iI)==2
            && GetEntData(iI,g_iIncapO) != 0 ) // 获取倒地玩家列表
        {
            iCount++;
            iCid[iCount]=iI;

            //----DEBUG----
            //PrintToChatAll("\x03-incap registering \x01%i",iI);
        }
    }

    //if the first two client ids are null, or
    //if the count was zero OR one, return
    //since someone can't self-revive if they're
    //the only ones incapped!
    if (iCount<=0           // 倒地的ID为空或者有且只有一个倒地，不自起
        || iCid[1]<=0
        || iCid[2]<=0)
        return;

    //----DEBUG----
    //PrintToChatAll("\x03-beginning self-revive checks, iCount=\x01%i",iCount);

    //now we check for someone to revive 检查自救条件，且一次只允许一个人自救
    //and we only revive one person at a time
    for (new iI=1 ; iI<=iCount ; iI++)
    {
        //----DEBUG----
        //PrintToChatAll("\x03- iledge value \x01%i",GetEntData(iCid[iI],g_iLedgeO));

        //client ids are stored incrementally (X in 1, Y in 2, Z in 3,...)
        //in the array iCid[], and iI increases per tick, hence this mess =P
        //in short, here we use iCid[iI], NOT iI!
        if (g_iConfirm[iCid[iI]]==1 // 玩家身份确认
            && g_iSur2[iCid[iI]]==2// 当前玩家选择黄金战甲技能
            && g_iMyDisabler[iCid[iI]] == -1 // 是否可自由行动
            && g_iSpiritCooldown[iCid[iI]]==0// 黄金战甲CD
            && IsClientInGame(iCid[iI])==true// 游戏中存在此玩家
            && IsPlayerAlive(iCid[iI])==true// 玩家未死亡
            && GetClientTeam(iCid[iI])==2)// 玩家为幸存者
        {
            //----DEBUG----
            //PrintToChatAll("\x03-reviving \x01%i",iCid[iI]);

            //retrieve revive count 获取自起次数
            new iRevCount_ret = GetEntData(iCid[iI], g_iRevCountO, 1);

            //create a data pack to pass down info 黑白记录
            //so we effectively only execute spirit's state changes
            //if we detect that the self-revive did in fact execute
            new Handle:hPack = CreateDataPack();
            WritePackCell(hPack,iCid[iI]);
            WritePackCell(hPack,iRevCount_ret);

            //here we give health through the console command 通过控制台给予黄金战甲玩家健康值
            //which is used to revive the player (no other way
            //I know of, setting the m_isIncapacitated in
            //CTerrorPlayer revives them but they can't move!)
            new iflags=GetCommandFlags("give");
            SetCommandFlags("give", iflags & ~FCVAR_CHEAT);
            FakeClientCommand(iCid[iI],"give health");
            SetCommandFlags("give", iflags);

            //and remove their health here (since "give health" gives them 100!) 执行黄金战甲血量修正事件
            CreateTimer(0.5,Spirit_ChangeHP,hPack);

            //here we check if there's anyone else with
            //the spirit perk who's also incapped, so we
            //know if we should continue allowing crawling

            //first, check if crawling adjustments are allowed
            //if not, then just break right away
            /*if (g_iSpirit_crawling==0
                || g_iSpirit_enable==0)
                break;

            new iCrawlClient=-1;
            for (new iI2=1 ; iI2<=MaxClients ; iI2++)
            {
                if (g_iConfirm[iI2]==0) continue;
                if (g_iSur1[iI2]==3
                    && g_iPIncap[iI2]!=0)
                {
                    iCrawlClient=iI2;
                    break;
                }
            }
            if (iCrawlClient>0)
                SetConVarInt(FindConVar("survivor_allow_crawling"),1,false,false);
            else
                SetConVarInt(FindConVar("survivor_allow_crawling"),0,false,false);*/

            //finally, since spirit fired, break the loop
            //since we only want one person to self-revive at a time
            //return;
        }
    }
    return;
}



//cooldown timer
public Action:Spirit_CooldownTimer (Handle:timer, any:iCid)
{
    KillTimer(timer);
    g_iSpiritTimer[iCid]=INVALID_HANDLE;
    //if the cooldown's been turned off,
    //that means a new round has started
    //and we can skip everything here
    if (IsServerProcessing()==false
        || g_iSpiritCooldown[iCid]==0)
        return Plugin_Stop;

    g_iSpiritCooldown[iCid]=0;

    //and this sends the client a hint message
    if (IsClientInGame(iCid)==true
        && IsPlayerAlive(iCid)==true
        && GetClientTeam(iCid)==2
        && IsFakeClient(iCid)==false)
        PrintHintText(iCid,"%t", "SpiritTimerFinishedMessage");

    return Plugin_Stop;
}

//timer for removing hp
//(like juggernaut, removing it too quickly
//confuses the game and doesn't remove it =/)
public Action:Spirit_ChangeHP (Handle:timer, any:hPack)
{
    //----DEBUG----
    //PrintToChatAll("\x05spirit\x03 init changehp");

    //retrieve vars from pack
    ResetPack(hPack);
    new iCid = ReadPackCell(hPack);
    new iRevCount_ret = ReadPackCell(hPack);
    CloseHandle(hPack);

    //only execute spirit functions after checks pass
    if (IsServerProcessing()==true
        && IsClientInGame(iCid) == true
        && GetEntData(iCid,g_iIncapO) == 0
        && IsPlayerAlive(iCid)==true
        && GetClientTeam(iCid)==2)
    {
        //----DEBUG----
        //PrintToChatAll("\x05spirit\x03 checks passed");

        //set revive count after self-revive
        SetEntData(iCid, g_iRevCountO, iRevCount_ret+1, 1);
        if (iRevCount_ret+1 >= 2)
        {
            //borrowed from Crimson Fox's Black and White Defib code
            SetEntProp(iCid, Prop_Send, "m_isGoingToDie", 1);
            PrintHintText(iCid,"你已经处于黑白状态!");
        }
        //and we give them bonus health buffer here
        SetEntDataFloat(iCid,g_iHPBuffO, GetEntDataFloat(iCid,g_iHPBuffO)+g_iSpirit_buff ,true);
        //set their health back to 1
		//PrintToChatAll("自起");
        SetEntityHealth(iCid,1);

        //get the proper cd number for the game mode
        new iTime;
        if (g_iL4D_GameMode==2)
            iTime=g_iSpirit_cd_vs;
        else if (g_iL4D_GameMode==1)
            iTime=g_iSpirit_cd_sur;
        else
            iTime=g_iSpirit_cd;

        //spirit-specific functions
        g_iSpiritTimer[iCid]=CreateTimer(iTime*1.0,Spirit_CooldownTimer,iCid);
        g_iPIncap[iCid]=0;
        g_iSpiritCooldown[iCid]=1;
        //show a message if it's not a bot
        if (IsFakeClient(iCid)==false)
            PrintHintText(iCid,"黄金战甲: %t!", "SpritSuccessMessage");
    }

    //always destroy the timer, since it's possible spirit may not have executed
    KillTimer(timer);
    return Plugin_Stop;
}



//=============================
// Sur2: Helping Hand       救护专家技能源码
//=============================

//fired before reviving begins,reduces revive time
HelpHand_OnReviveBegin (iCid)
{
    //PrintToChatAll("营救_%N", iCid);
    //check if cvar changes are allowed
    //for this perk; if not, then stop
    if (g_iHelpHand_convar==0)
        return 0;

    //check if perk is enabled
    if (g_iSur2_enable==0
        || g_iHelpHand_enable==0        &&    g_iL4D_GameMode==0
        || g_iHelpHand_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iHelpHand_enable_vs==0        &&    g_iL4D_GameMode==2)
        return 0;

    //----DEBUG----
    //PrintToChatAll("\x03revive begin detected, reviver: \x01%i\x03, subject: \x01%i",iCid,iSub);

    //check for helping hand
    if (g_iSur2[iCid]==3
        && g_iConfirm[iCid]==1)
    {
        //----DEBUG----
        //PrintToChatAll("\x03-perk present, setting revive time to \x01%f",g_flReviveTime/2);
        //PrintToChatAll("救护医师拉人时间_____%f", g_flHelpHand_timemult);

        SetConVarFloat(FindConVar("survivor_revive_duration"), g_flHelpHand_timemult ,false,false);
        return 0;
    }

    //otherwise, reset the revive duration
    else
    {
        //----DEBUG----
        //PrintToChatAll("\x03-no perk, attempting to reset revive time to \x01%f",g_flReviveTime);
        //PrintToChatAll("非——救护医师拉人时间____%f",g_flReviveTime);
        SetConVarFloat(FindConVar("survivor_revive_duration"),g_flReviveTime,false,false);
        return 0;
    }
}

HelpHand_OnReviveSuccess (iCid, iSub, iLedge)
{
    //----DEBUG----
    //PrintToChatAll("\x05helphand\x03 reviver: \x01%i\x03, subject: \x01%i",iCid,iSub);

    //then check for helping hand
    if (g_iSur2[iCid]==3
        && g_iConfirm[iCid]==1
        && g_iSur2_enable==1
        && (g_iHelpHand_enable==1        &&    g_iL4D_GameMode==0
        || g_iHelpHand_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iHelpHand_enable_vs==1        &&    g_iL4D_GameMode==2))
    {
        switch (iLedge)
        {
        case 1:
            {
                g_iMyDisabler[iSub] = -1;

                //----DEBUG----
                //PrintToChatAll("\x03-ledge hang save detected");
            }
        case 0:
            {
                //----DEBUG----
                //PrintToChatAll("\x03-m_healthBuffer offset: \x01%i",iHPBuffO);
                //PrintToChatAll("\x03-client \x01%i\x03 value at offset: \x01%f",iSub,GetEntDataFloat(iSub,g_iHPBuffO));

                decl iBuff;
                if (g_iL4D_GameMode==2)
                    iBuff=g_iHelpHand_buff_vs;
                else
                    iBuff=g_iHelpHand_buff;

                SetEntDataFloat(iSub,g_iHPBuffO, GetEntDataFloat(iSub,g_iHPBuffO)+iBuff ,true);

                CreateTimer(0.5, HelpHand_Delayed, iCid);

                //----DEBUG----
                //PrintToChatAll("\x03-value at offset, post-mod: \x01%f",GetEntDataFloat(iSub,g_iHPBuffO));

                new String:st_name[24];
                GetClientName(iSub,st_name,24);
                PrintHintText(iCid,"救护医生技能: %t %s!", "HelpingHandDonorHint", st_name);
                GetClientName(iCid,st_name,24);
                PrintHintText(iSub,"救护医生技能: %s %t",st_name, "HelpingHandReceiverHint");
            }
        }
    }

    //----DEBUG----
    //PrintToChatAll("\x03-revive end, attempting to reset revive time to \x01%f",g_flReviveTime);

    //only adjust the convar if
    //convar changes are allowed
    //for this perk
    if (g_iHelpHand_convar==1
        && g_iSur2_enable==1
        && (g_iHelpHand_enable==1        &&    g_iL4D_GameMode==0
        || g_iHelpHand_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iHelpHand_enable_vs==1        &&    g_iL4D_GameMode==2))
        {        
            //PrintToChatAll("还原救护医师CD___%f__",g_flReviveTime);
            SetConVarFloat(FindConVar("survivor_revive_duration"),g_flReviveTime,false,false);
        }

    //and then check if we need to continue allowing crawling
    //by running checks through everyone...
    //...but first, check if spirit convar changes are allowed
    /*if (g_iSur1_enable==1
        && g_iSpirit_crawling==1
        && (g_iSpirit_enable==1        &&    g_iL4D_GameMode==0
        || g_iSpirit_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iSpirit_enable_vs==1    &&    g_iL4D_GameMode==2))
    {
        new iCrawlClient=-1;
        for (new iI2=1 ; iI2<=MaxClients ; iI2++)
        {
            if (g_iConfirm[iI2]==0) continue;
            if (g_iSur2[iI2]==2
                && g_iPIncap[iI2]!=0)
            {
                iCrawlClient=iI2;
                break;
            }
        }
        if (iCrawlClient>0)
            SetConVarInt(FindConVar("survivor_allow_crawling"),1,false,false);
        else
            SetConVarInt(FindConVar("survivor_allow_crawling"),0,false,false);
    }*/

    return 0;
}

public Action:HelpHand_Delayed (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==true
        && IsValidEntity(iCid)==true
        && IsClientInGame(iCid)==true
        && GetClientTeam(iCid)==2)
    {
        //----DEBUG----
        //PrintToChatAll("\x05helphand\x03 attempting to give reviver bonus to \x01%i",iCid);
        //new g_iHPBuffTimeO;
        //g_iHPBuffTimeO = FindSendPropOffs("CTerrorPlayer","m_healthBufferTime");
        //PrintToChatAll("\x03- health buffer time \x01%i %f",g_iHPBuffTimeO, GetEntDataFloat(iCid,g_iHPBuffTimeO));

        decl iBuff;
        if (g_iL4D_GameMode==2)
            iBuff=g_iHelpHand_buff_vs;
        else
            iBuff=g_iHelpHand_buff;

        //SetEntProp(iCid,Prop_Data,"m_iHealth", GetEntProp(iCid,Prop_Data,"m_iHealth")+ iBuff/3 );

        SetEntDataFloat(iCid, g_iHPBuffTimeO, GetGameTime(), true);
        new Float:flBuff_ret = GetEntDataFloat(iCid,g_iHPBuffO);
        if (flBuff_ret <= 0)
            flBuff_ret = 0.0;
        SetEntDataFloat(iCid, g_iHPBuffO, flBuff_ret + iBuff/2 , true);
    }

    KillTimer(timer);
    return Plugin_Stop;
}



//=============================
// Sur3: Pack Rat       弹药专家
//=============================

//on gun pickup
PR_Pickup(iCid, String:stWpn[])
{
    if (g_iSur3[iCid]==1
        && g_iSur2_enable==1
        && (g_iPack_enable==1        &&    g_iL4D_GameMode==0
        || g_iPack_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iPack_enable_vs==1        &&    g_iL4D_GameMode==2))
    {
        if (StrContains(stWpn, "smg", false)!= -1
            || StrContains(stWpn, "rifle", false)!= -1
            || StrContains(stWpn, "shotgun", false)!= -1
            || StrContains(stWpn, "sniper", false)!= -1    )
        {
            PR_GiveFullAmmo(iCid);
        }
    }
}

//on ammo pickup, check if pack rat is in effect
public Event_AmmoPickup (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0)
        return;

    if (g_iSur3[iCid]==1
        && g_iConfirm[iCid]==1
        && g_iSur3_enable==1
        && (g_iPack_enable==1            &&    g_iL4D_GameMode==0
            || g_iPack_enable_sur==1    &&    g_iL4D_GameMode==1
            || g_iPack_enable_vs==1        &&    g_iL4D_GameMode==2))
    {
        PR_GiveFullAmmo(iCid);
    }
}

//gives full ammo
PR_GiveFullAmmo (iCid)
{
    //formula: max + pack rat + max clip size - currently in clip
    //new iAmmoO=FindDataMapOffs(iCid,"m_iAmmo");

    if (g_iL4D_12 == 2)
    {
        if (g_bPRalreadyApplying[iCid] == false)
        {
            g_bPRalreadyApplying[iCid] = true;
            CreateTimer(0.1, PR_GiveFullAmmo_delayed, iCid);
        }
        else
            return;
    }
    else if (g_iL4D_12 == 1)
    {
        new iAmmoO=FindDataMapOffs(iCid,"m_iAmmo");
        decl iAmmoCount;

        //huntingrifle offset +8
        iAmmoCount = GetEntData(iCid, iAmmoO +8);
        SetEntData(iCid, iAmmoO    +8, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //rifle - offset +12
        iAmmoCount = GetEntData(iCid, iAmmoO +12);
        SetEntData(iCid, iAmmoO    +12, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //smg - offset +20
        iAmmoCount = GetEntData(iCid, iAmmoO +20);
        SetEntData(iCid, iAmmoO    +20, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //shotgun - offset +24
        iAmmoCount = GetEntData(iCid, iAmmoO +24);
        SetEntData(iCid, iAmmoO    +24, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
    }
}

//new technique - instead of running off a convar, adjusts ammo
//relative to what player already has in inventory after a delay
public Action:PR_GiveFullAmmo_delayed (Handle:timer, any:iCid)
{
    KillTimer(timer);

    if (g_bPRalreadyApplying[iCid] == true)
        g_bPRalreadyApplying[iCid] = false;
    else
        return Plugin_Stop;
    
    if (IsServerProcessing()==false
        || IsValidEntity(iCid) == false
        || IsClientInGame(iCid) == false
        || IsPlayerAlive(iCid)==false
        || GetClientTeam(iCid)!=2)
        return Plugin_Stop;

    new iAmmoO=FindDataMapOffs(iCid,"m_iAmmo");
    decl iAmmoO_offset;
    decl iAmmoCount;

    //checks each weapon type ammo in player's inventory
    //if non-zero, then assume player has that weapon
    //and adjust only that weapon's ammo

    //----DEBUG----
    //new iI = 0;
    //PrintToChatAll("\x05PR\x03 being feedback loop");
    //while (iI <= 64)
    //{
        //iAmmoCount = GetEntData(iCid, iAmmoO + iI);
        //PrintToChatAll("\x05PR\x03 iI = \x01%i\x03, value = \x01%i",iI, iAmmoCount);
        //iI++;
    //}

    //rifle - offset +12
    iAmmoO_offset = 12;
    iAmmoCount = GetEntData(iCid, iAmmoO + iAmmoO_offset);
    if (iAmmoCount > 0)
    {
        SetEntData(iCid, iAmmoO    + iAmmoO_offset, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //return Plugin_Stop;
    }
    //smg - offset +20
    iAmmoO_offset = 20;
    iAmmoCount = GetEntData(iCid, iAmmoO + iAmmoO_offset);
    if (iAmmoCount > 0)
    {
        SetEntData(iCid, iAmmoO    + iAmmoO_offset, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //return Plugin_Stop;
    }
    //auto-shotgun - now offset +32
    iAmmoO_offset = 32;
    iAmmoCount = GetEntData(iCid, iAmmoO + iAmmoO_offset);
    if (iAmmoCount > 0)
    {
        SetEntData(iCid, iAmmoO    + iAmmoO_offset, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //return Plugin_Stop;
    }
    //pump shotgun - now offset +28
    iAmmoO_offset = 28;
    iAmmoCount = GetEntData(iCid, iAmmoO + iAmmoO_offset);
    if (iAmmoCount > 0)
    {
        SetEntData(iCid, iAmmoO    + iAmmoO_offset, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //return Plugin_Stop;
    }
    //huntingrifle offset +32 - now +36
    iAmmoO_offset = 36;
    iAmmoCount = GetEntData(iCid, iAmmoO + iAmmoO_offset);
    if (iAmmoCount > 0)
    {
        SetEntData(iCid, iAmmoO    + iAmmoO_offset, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //return Plugin_Stop;
    }
    //militarysniper offset +36 - now +40
    iAmmoO_offset = 40;
    iAmmoCount = GetEntData(iCid, iAmmoO + iAmmoO_offset);
    if (iAmmoCount > 0)
    {
        SetEntData(iCid, iAmmoO    + iAmmoO_offset, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //return Plugin_Stop;
    }
    //grenade launcher offset +64
    iAmmoO_offset = 64;
    iAmmoCount = GetEntData(iCid, iAmmoO + iAmmoO_offset);
    if (iAmmoCount > 0)
    {
        SetEntData(iCid, iAmmoO    + iAmmoO_offset, RoundToNearest(iAmmoCount * (1 + g_flPack_ammomult)) );
        //return Plugin_Stop;
    }

    return Plugin_Stop;
}



//=============================
// Sur3: Chem Reliant       枪支专家
//=============================

//on drug used
Chem_OnDrugUsed (iCid)
{
    //check if perk is enabled
    if (g_iSur3_enable==0
        || g_iChem_enable==0            &&    g_iL4D_GameMode==0
        || g_iChem_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iChem_enable_vs==0        &&    g_iL4D_GameMode==2)
        return 0;

    //----DEBUG----
    //PrintToChatAll("\x03Pill user: \x01%i", iCid);

    if (g_iSur3[iCid]==2
        && g_iConfirm[iCid]==1)
    {
        new Float:flBuff=GetEntDataFloat(iCid,g_iHPBuffO);
        new iHP=GetEntProp(iCid,Prop_Data,"m_iHealth");
        
        //so we need to test the maxbound for
        //how much health buffer we can give
        //which can vary depending on whether
        //they have unbreakable or not

        //CASE 1: HAS UNBREAKABLE
        if (g_iSur2[iCid]==1
            && g_iSur3_enable==1
            && (g_iUnbreak_enable==1    &&    g_iL4D_GameMode==0
            || g_iUnbreak_enable_sur==1    &&    g_iL4D_GameMode==1
            || g_iUnbreak_enable_vs==1    &&    g_iL4D_GameMode==2))
        {
            //CASE 1A:
            //combined health + chem reliant < max health possible
            if (flBuff + iHP + g_iChem_buff < 100 + g_iUnbreak_hp)
                //this is the easiest, just give them chem reliant bonus
                SetEntDataFloat(iCid,g_iHPBuffO, flBuff+g_iChem_buff ,true);

            //CASE 1B:
            //combined health + chem reliant > max health possible
            else
                //this is a bit trickier, give them the difference
                //between the max health possible and their current health
                SetEntDataFloat(iCid,g_iHPBuffO, (100.0+g_iUnbreak_hp)-iHP ,true);
        }
        //CASE 2: DOES NOT HAVE UNBREAKABLE
        else
        {
            //CASE 1A:
            //combined health + chem reliant < max health possible
            if (flBuff + iHP + g_iChem_buff < 100)
                //this is the easiest, just give them chem reliant bonus
                SetEntDataFloat(iCid,g_iHPBuffO, flBuff+g_iChem_buff ,true);

            //CASE 1B:
            //combined health + chem reliant > max health possible
            else
                //this is a bit trickier, give them the difference
                //between the max health possible and their current health
                SetEntDataFloat(iCid,g_iHPBuffO, 100.0-iHP ,true);
        }
    }
    return 0;
}

//called on roundstart or on confirming perks,
//gives pills off the start
Event_Confirm_ChemReliant (iCid)
{
    if (iCid==0
        || GetClientTeam(iCid)!=2
        || IsPlayerAlive(iCid)==false
        || g_iConfirm[iCid]==0
        || g_iSur3[iCid]!=2)
        return;

    //check if perk is enabled
    if (g_iSur3_enable==0
        || g_iChem_enable==0        &&    g_iL4D_GameMode==0
        || g_iChem_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iChem_enable_vs==0        &&    g_iL4D_GameMode==2)
        return;

    new iflags=GetCommandFlags("give");
    SetCommandFlags("give", iflags & ~FCVAR_CHEAT);
    
    new fuwuqi = GetRandomInt(0, 2);            //随机获得枪支
    switch( fuwuqi)
        {
            case 0: 
            {

            
            }
            case 1: 
            {
            
            }
            case 2: 
            {
                FakeClientCommand(iCid,"give weapon_chainsaw");    //枪支专家物品修改 额外得到
            }
        }
    new liudanzhuanjia = GetRandomInt(0, 8);            //随机获得枪支
    switch( liudanzhuanjia)
        {
            case 0: 
            {
            FakeClientCommand(iCid,"give rifle");
            FakeClientCommand(iCid,"give rifle_ak47");
            FakeClientCommand(iCid,"give rifle_m60");
            }
            case 1: 
            {
            FakeClientCommand(iCid,"give rifle");
            FakeClientCommand(iCid,"give weapon_sniper_scout");
            FakeClientCommand(iCid,"give rifle");
            }
            case 2: 
            {
            FakeClientCommand(iCid,"give rifle");
            FakeClientCommand(iCid,"give rifle_m60");
            FakeClientCommand(iCid,"give weapon_sniper_awp");
            }
            case 3: 
            {
            FakeClientCommand(iCid,"give rifle");
            FakeClientCommand(iCid,"give rifle_ak47");
            FakeClientCommand(iCid,"give weapon_sniper_scout");
            }
            case 4: 
            {
            FakeClientCommand(iCid,"give weapon_sniper_awp");
            FakeClientCommand(iCid,"give weapon_sniper_awp");
            FakeClientCommand(iCid,"give rifle_ak47");
            }
            case 5:
            {
            FakeClientCommand(iCid,"give weapon_sniper_scout");
            FakeClientCommand(iCid,"give weapon_sniper_awp");
            FakeClientCommand(iCid,"give rifle_m60");
            }
            case 6:
            {
            FakeClientCommand(iCid,"give sniper_military");
            FakeClientCommand(iCid,"give weapon_sniper_scout");
            FakeClientCommand(iCid,"give rifle_ak47");
            }
            case 7:
            {
            FakeClientCommand(iCid,"give weapon_sniper_awp");
            FakeClientCommand(iCid,"give  sniper_military");
            FakeClientCommand(iCid,"give rifle_ak47");
            }
            case 8:
            {
            FakeClientCommand(iCid,"give  sniper_military");
            FakeClientCommand(iCid,"give rifle_m60");
            FakeClientCommand(iCid,"give hunting_rifle");
            }
            
        }
    //FakeClientCommand(i, iCid,"upgrade_add laser_sight");    //枪支专家物品修改 额外得到
    SetCommandFlags("give", iflags);

    return;
}



//=============================
// Sur3: Hard to Kill       坚韧之躯
//=============================

HardToKill_OnIncap (iCid)
{
    if (GetClientTeam(iCid)!=2
        || g_iConfirm[iCid]==0
        || GetClientTeam(iCid)!=2)
        return;

    if (g_iSur3_enable==0
        || g_iHard_enable==0        &&    g_iL4D_GameMode==0
        || g_iHard_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iHard_enable_vs==0        &&    g_iL4D_GameMode==2)
        return;

    if (g_iSur3[iCid]==3)
    {
        //new iHP=GetEntProp(iCid,Prop_Data,"m_iHealth");

        //----DEBUG----
        //PrintToChatAll("\x03hard to kill fire, client \x01%i\x03, health \x01%i",iCid,iHP);

        //SetEntProp(iCid,Prop_Data,"m_iHealth", iHP + RoundToNearest(iHP*g_flHard_hpmult) );
        //SetEntDataFloat(iCid,g_iHPBuffO, flHPBuff+300 ,true);
        CreateTimer(0.5,HardToKill_Delayed,iCid);

        //----DEBUG----
        //PrintToChatAll("\x03-postfire values, health \x01%i",GetEntProp(iCid,Prop_Data,"m_iHealth"));
    }
}

public Action:HardToKill_Delayed (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==true
        && IsValidEntity(iCid)==true
        && IsClientInGame(iCid)==true
        && GetClientTeam(iCid)==2)
    {
        new iHP=GetEntProp(iCid,Prop_Data,"m_iHealth");

        SetEntProp(iCid,Prop_Data,"m_iHealth", iHP + RoundToNearest(iHP*g_flHard_hpmult) );

        iHP = RoundToNearest( 300*(g_flHard_hpmult+1) );
        if (GetEntProp(iCid,Prop_Data,"m_iHealth") > iHP)
            SetEntProp(iCid,Prop_Data,"m_iHealth", iHP);
    }

    KillTimer(timer);
    return Plugin_Stop;
}



//=============================
// Sur3: Little Leaguer     棒球队员
//=============================

Event_Confirm_LittleLeaguer (iCid)
{
    if (iCid==0
        || GetClientTeam(iCid)!=2
        || IsPlayerAlive(iCid)==false
        || g_iConfirm[iCid]==0
        || g_iSur3[iCid]!=5)
        return;

    //check if perk is enabled
    if (g_iSur3_enable==0
        || g_iChem_enable==0        &&    g_iL4D_GameMode==0
        || g_iChem_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iChem_enable_vs==0        &&    g_iL4D_GameMode==2)
        return;

    new iflags=GetCommandFlags("give");
    SetCommandFlags("give", iflags & ~FCVAR_CHEAT);
    FakeClientCommand(iCid,"give baseball_bat");
    FakeClientCommand(iCid,"give baseball_bat");
    FakeClientCommand(iCid,"give baseball_bat");
    FakeClientCommand(iCid,"give baseball_bat");
    FakeClientCommand(iCid,"give baseball_bat");
    FakeClientCommand(iCid,"give baseball_bat");
    FakeClientCommand(iCid,"give baseball_bat");
    FakeClientCommand(iCid,"give baseball_bat");
    SetCommandFlags("give", iflags);

    return;
}



//=============================
// Sur3: Extreme Conditioning           凌波微步
//=============================

Extreme_Rebuild ()
{
    //if the server's not running or
    //is in the middle of loading, stop
    if (IsServerProcessing()==false)
        return;

    //check if perk is enabled
    if (g_iSur3_enable==0
        || g_iExtreme_enable==0        &&    g_iL4D_GameMode==0
        || g_iExtreme_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iExtreme_enable_vs==0    &&    g_iL4D_GameMode==2)
        return;

    //----DEBUG----
    //PrintToChatAll("\x03extreme cond rebuilding");

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        if (IsClientInGame(iI)==true
            && IsPlayerAlive(iI)==true
            && g_iSur3[iI]==4
            && g_iConfirm[iI]==1
            && GetClientTeam(iI)==2)
        {
            if (g_iSur3[iI]==4
                && g_iConfirm[iI]==1)
                SetEntDataFloat(iI,g_iLaggedMovementO, 1.0*g_flExtreme_rate ,true);
            else
                SetEntDataFloat(iI,g_iLaggedMovementO, 1.0 ,true);

            //----DEBUG----
            //PrintToChatAll("\x03-registering \x01%i",iI);
        }
    }
}




//=============================
// Inf1: Blind Luck     遗忘之咒
//=============================

BlindLuck_OnIt (iAtt, iVic)
{
    //don't blind bots as per grandwaziri's plugin, they suck enough anyways
    if (g_iInf1[iAtt]==2
        && g_iConfirm[iAtt]==1
        && IsFakeClient(iVic)==false)
    {
        //check if perk is enabled
        if (g_iInf1_enable==0
            || g_iBlind_enable==0)
            return;

        SetEntProp(iVic, Prop_Send, "m_iHideHUD", 64);

        //----DEBUG----
        //PrintToChatAll("\x03-attempting to hide hud");
    }
    return;
}

BlindLuck_OnSpawn (iCid)
{
    //stop if convar changes are disallowed for this perk
    if (g_iBlind_enable==0
        || g_iInf1_enable==0)
        return 0;

    if (g_iInf1[iCid]==2)
    {
        //----DEBUG----
        //PrintToChatAll("\x05drag\x03 creating timer");

        //CreateTimer(1.0,Timer_BlindLuckChecks,iCid,TIMER_REPEAT);
        ResetInfectedAbility(iCid, g_flBlind_cdmult);
    }
    
    return 0;
}

public Action:Timer_BlindLuckChecks (Handle:timer, any:iCid)
{
    PrintToChatAll("");        //插入实现技能修复
    if (IsServerProcessing()==false
        || iCid <= 0
        || IsClientInGame(iCid)==false
        || IsPlayerAlive(iCid)==false
        || GetEntData(iCid, g_iClassO)!=2)
    {
        PrintToChatAll("");        //插入实现技能修复
        KillTimer(timer);
        return Plugin_Stop;
    }

    new iEntid = GetEntDataEnt2(iCid,g_iAbilityO);
    if (iEntid == -1)
    {
        PrintToChatAll("");        //插入实现技能修复
        KillTimer(timer);
        PrintToChatAll("");        //插入实现技能修复
        return Plugin_Stop;
    }
    new ability = GetEntPropEnt(iCid, Prop_Send, "m_customAbility");
    PrintToChatAll("");        //插入实现技能修复
    new Float:flTimeStamp_ret = GetEntDataFloat(iEntid,g_iNextActO+8);
    PrintToChatAll("");        //插入实现技能修复
    if (g_flTimeStamp[iCid] < flTimeStamp_ret)
    PrintToChatAll("");        //插入实现技能修复
    {
        PrintToChatAll("");        //插入实现技能修复
        g_flTimeStamp[iCid] = flTimeStamp_ret;
        PrintToChatAll("");        //插入实现技能修复
        new Float:flTimeStamp_calc = flTimeStamp_ret - (GetConVarFloat(FindConVar("z_vomit_interval")) * (1 - g_flBlind_cdmult) );
        PrintToChatAll("");        //插入实现技能修复
        SetEntDataFloat(iEntid, g_iNextActO+8, flTimeStamp_calc, true);
        PrintToChatAll("");        //插入实现技能修复
        
        
        {
            PrintToChatAll("");        //插入实现技能修复
            SetConVarFloat(FindConVar("z_vomit_fatigue"),0.0,false,false);
            SetEntDataFloat(iCid,g_iLaggedMovementO, 0.4*g_flMotion_rate ,true);    //在遗忘之咒叠加身体轻盈效果
        }
        // 胖子CD修正
        if (ability > 0)
		{
			SetEntPropFloat(ability, Prop_Send, "m_duration", g_flBlind_cdmult);
			SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + g_flBlind_cdmult);
		}
        
        
    }
    PrintToChatAll("");        //插入实现技能修复
    return Plugin_Continue;
}



//=============================
// Inf1: Barf Bagged        恶魔唤醒
//=============================

BarfBagged_OnIt (iAtt)
{
    //only spawn a mob if one guy got slimed
    //or if all four got slimed (max 2 extra mobs)
    if (g_iInf1[iAtt]==1
        && g_iConfirm[iAtt]==1
        && (g_iSlimed==1 || g_iSlimed==4))
    {
        //check if perk is enabled
        if (g_iInf1_enable==0
            || g_iBarf_enable==0)
            return 0;

        //----DEBUG----
        //PrintToChatAll("\x03-attempting to spawn a mob, g_iSlimed=\x01%i",g_iSlimed);

        new iflags=GetCommandFlags("z_spawn_old");
        SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
        
        new RandomGiv = GetRandomInt(0, 7);        //随机喷出一只女巫
        switch(RandomGiv)
        {
            case 0: 
            {

            }
            case 1: 
            {
                FakeClientCommand(iAtt,"z_spawn_old witch");
                if (g_iSlimed==4) PrintHintText(iAtt,"恶魔唤醒技能： %t", "BarfBaggedMobHint");
            }
            case 2: 
            {

            }
            case 3: 
            {

            }
            case 4: 
            {

            }
            case 5: 
            {
                FakeClientCommand(iAtt,"z_spawn_old witch");
                if (g_iSlimed==4) PrintHintText(iAtt,"恶魔唤醒技能： %t", "BarfBaggedMobHint");
            }
            case 6: 
            {

            }
            case 7: 
            {

            }
            case 8: 
            {

            }
            case 9: 
            {

            }
        }
        FakeClientCommand(iAtt,"give health");
        SetCommandFlags("z_spawn_old", iflags);
    }
    return 0;
}



//=============================
// Inf1: Dead Wreckening        致命袭击
//=============================

//damage add
DeadWreckening_DamageAdd (iAtt, iVic, iType, iDmgOrig)
{
    if (iAtt==0
        && iType==128
        && g_iSlimed>0
        && g_iConfirm[g_iSlimerLast]==1
        && g_iInf1[g_iSlimerLast]==3)
    {
        //check if perk is enabled
        if (g_iInf1_enable==0
            || g_iDead_enable==0)
            return 1;

        //----DEBUG----
        //PrintToChatAll("\x03dead wreckening fire");

        new iDmgAdd = DamageAddRound (iDmgOrig, g_flDead_dmgmult);

        if (iDmgAdd==0)
            return 0;

        InfToSurDamageAdd(iVic, iDmgAdd ,iDmgOrig);
        
        return 1;
    }
    return 0;
}



//=============================
// Inf1: Motion Sickness            身体轻盈
//=============================

Motion_OnSpawn (iCid)
{
    //stop here if the perk is disabled
    if (g_iMotion_enable==0
        || g_iInf1_enable==0)
        return 0;

    //check for motion sickness
    if (g_iInf1[iCid]==4
        && g_iConfirm[iCid]==1)
    {
        SetConVarFloat(FindConVar("z_vomit_fatigue"),0.0,false,false);
        SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0*g_flMotion_rate ,true);
    }
    else
        SetConVarFloat(FindConVar("z_vomit_fatigue"), g_flVomitFatigue ,false,false);

    return 0;
}



//=============================
// Inf3: Tongue Twister     夺命舌速
//=============================

TongueTwister_OnAbilityUse (iCid, String:stAb[])
{
    //check for smoker-type perks
    if (StrEqual(stAb,"ability_tongue",false)==true)
    {
        //stop if twister is disabled
        if (g_iTongue_enable==0)
            return 0;

        //check for twister
        if (g_iInf3[iCid]==1)
        {
            PrintToChatAll("%N 夺命速舌__速度 __ %f", iCid, g_flTongueRange*g_flTongue_rangemult);
            SetConVarFloat(FindConVar("tongue_fly_speed"), g_flTongueFlySpeed*g_flTongue_speedmult ,false,false);
            SetConVarFloat(FindConVar("tongue_range"), g_flTongueRange*g_flTongue_rangemult ,false,false);
        }
        else
        {
			PrintToChatAll("%N 正常舌头__速度 __ %f", iCid, g_flTongueRange);
            SetConVarFloat(FindConVar("tongue_fly_speed"),g_flTongueFlySpeed,false,false);
            SetConVarFloat(FindConVar("tongue_range"),g_flTongueRange,false,false);
        }
    }
    return 0;
}

TongueTwister_OnTongueGrab (iCid)
{
    //stop if twister is disabled
    if (g_iInf3_enable==0
        || g_iTongue_enable==0)
        return 0;

    //----DEBUG----
    //PrintToChatAll("\x03yoink grab fired, client: \x01%i",iCid);

    if (g_iConfirm[iCid]==1
        && g_iInf3[iCid]==1)
        SetConVarFloat(FindConVar("tongue_victim_max_speed"), g_flTongueSpeed*g_flTongue_pullmult ,false,false);
    else
        SetConVarFloat(FindConVar("tongue_victim_max_speed"),g_flTongueSpeed,false,false);

    return 0;
}

TongueTwister_OnTongueRelease ()
{
    //only reset these convars if twister is enabled
    if (g_iInf3_enable==1
        && g_iTongue_enable==1)
    {
        //SetConVarFloat(FindConVar("tongue_victim_max_speed"),g_flTongueSpeed,false,false);
        //SetConVarFloat(FindConVar("tongue_fly_speed"),g_flTongueFlySpeed,false,false);
        CreateTimer(3.0,Timer_TongueRelease,0);
    }

    return 0;
}

public Action:Timer_TongueRelease (Handle:timer, any:data)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    SetConVarFloat(FindConVar("tongue_victim_max_speed"),g_flTongueSpeed,false,false);
    SetConVarFloat(FindConVar("tongue_fly_speed"),g_flTongueFlySpeed,false,false);

    return Plugin_Stop;
}

TongueTwister_OnSpawn (iCid)
{
    //stop here if twister is disabled
    if (g_iInf3_enable==0
        || g_iTongue_enable==0)
        return;

    //check for tongue twister
    if (g_iInf3[iCid]==1
        && g_iConfirm[iCid]==1)
    {

        SetConVarFloat(FindConVar("tongue_range"), g_flTongueRange*g_flTongue_rangemult ,false,false);
        //----DEBUG----
        //PrintToChatAll("\x03-tongue range modified");
        //PrintToChatAll("夺命速舌__范围 __ %N", iCid);
    }

    //otherwise, just reset convar
    else
    {
        SetConVarFloat(FindConVar("tongue_range"),g_flTongueRange,false,false);

        //----DEBUG----
        //PrintToChatAll("\x03-tongue range reset");
    }
}



//=============================
// Inf3: Squeezer           致命之怀
//=============================

//damage add function
Squeezer_DamageAdd (iAtt, iVic, iTA, String:stWpn[], iDmgOrig)
{
    if (iTA==3
        && g_iConfirm[iAtt]==1
        && StrEqual(stWpn,"smoker_claw")==true
        && g_iInf3[iAtt]==2
        && g_iMyDisableTarget[iAtt] == iVic)
    {
        //stop if perk is disabled
        if (g_iInf3_enable==0
            || g_iSqueezer_enable==0)
            return 1;

        new iDmgAdd = DamageAddRound (iDmgOrig, g_flSqueezer_dmgmult);

        if (iDmgAdd==0)
            return 0;

        InfToSurDamageAdd(iVic, iDmgAdd ,iDmgOrig);

        return 1;
    }
    return 0;
}

//=============================
// Inf3: Drag and Drop      控舌
//=============================

//alters cooldown to be faster
Drag_OnTongueGrab (iCid)
{
    //----DEBUG----
    //PrintToChatAll("\x03drag and drop running checks");

    //stop if drag and drop is disabled
    if (g_iInf3_enable==0
        || g_iDrag_enable==0)
        return;

    //if attacker id is null, reset vars
    if (iCid<=0) 
    {
        SetConVarInt(FindConVar("tongue_allow_voluntary_release"),0,false,false);
        SetConVarFloat(FindConVar("tongue_player_dropping_to_ground_time"),g_flTongueDropTime,false,false);
        return ;
    }

    //check for drag and drop
    if (g_iInf3[iCid]==3
        && g_iConfirm[iCid]==1)
    {
        //----DEBUG----
        //PrintToChatAll("\x03and again! reducing cooldown to \x01%f",g_flTongueHitDelay/2);

        SetConVarInt(FindConVar("tongue_allow_voluntary_release"),1,false,false);
        SetConVarFloat(FindConVar("tongue_player_dropping_to_ground_time"),0.2,false,false);

        //CreateTimer(0.5,Timer_DragChecks,iCid,TIMER_REPEAT);

        return ;
    }

    //all else fails, reset vars
    else
    {
        //----DEBUG----
        //PrintToChatAll("\x03and again! resetting cooldown to \x01%f",g_flTongueHitDelay);

        SetConVarInt(FindConVar("tongue_allow_voluntary_release"),0,false,false);
        SetConVarFloat(FindConVar("tongue_player_dropping_to_ground_time"),g_flTongueDropTime,false,false);
        return ;
    }
}

Drag_OnSpawn(iCid)
{
    //stop if grasshopper is disabled
    if (g_iInf3_enable==0
        || g_iDrag_enable==0)
        return 0;

    if (GetClientTeam(iCid)==3
        && g_iInf3[iCid]==3
        && g_iConfirm[iCid]==1)
    {
        //----DEBUG----
        //PrintToChatAll("\x05drag\x03 creating timer");

        //CreateTimer(1.0,Timer_DragChecks,iCid,TIMER_REPEAT);
        ResetInfectedAbility(iCid, g_flDrag_cdmult);
        return 1;
    }

    return 0;
}

public Action:Timer_DragChecks (Handle:timer, any:iCid)
{
    //INITIAL CHECKS
    //--------------
    if (IsServerProcessing()==false
        || iCid <= 0
        || IsClientInGame(iCid)==false
        || IsPlayerAlive(iCid)==false
        || GetEntData(iCid, g_iClassO)!=1)
    {
        //----DEBUG----
        //if (IsServerProcessing()==false)            PrintToChatAll("\x03- server not processing, stopping");
        //else if (iCid <= 0)                            PrintToChatAll("\x03- icid <= 0, stopping, client id \x01%i", iCid);
        //else if (IsClientInGame(iCid)==false)        PrintToChatAll("\x03- client not in game, stopping");
        //else if (IsPlayerAlive(iCid)==false)        PrintToChatAll("\x03- client not alive, stopping");
        //else if (GetEntData(iCid, g_iClassO)!=1)    PrintToChatAll("\x03- class not correct, stopping, class id \x01%i", GetEntData(iCid, g_iClassO));

        KillTimer(timer);
        return Plugin_Stop;
    }

    //----DEBUG----
    //PrintToChatAll("\x03- \x05drag\x03 tick");
    //RETRIEVE VARIABLES
    //------------------
    //get the ability ent id
    new iEntid = GetEntDataEnt2(iCid,g_iAbilityO);
    //if the retrieved gun id is -1, then move on
	
	new ability = GetEntPropEnt(iCid, Prop_Send, "m_customAbility");
	
    if (iEntid == -1)
    {
        //----DEBUG----
        //PrintToChatAll("\x03- ientid == -1, stopping");

        KillTimer(timer);
        return Plugin_Stop;
    }
    //retrieve the next act time
    new Float:flDuration_ret = GetEntDataFloat(iEntid,g_iNextActO+4);

    //----DEBUG----
    //if (g_iShow==1)
    //    PrintToChatAll("\x03- actsuppress dur \x01 %f\x03 timestamp \x01%f", GetEntDataFloat(iEntid, g_iSuppressO+4), GetEntDataFloat(iEntid, g_iSuppressO+8) );

    //CHECK 1: PAUSE?
    //---------------
    //Valve seems to have a weird way of forcing a
    //pause before the cooldown timer starts: by setting
    //the timers to some arbitrarily high number =/
    //IIRC no cooldown exceeds 100s (highest is 30?) so
    //if any values exceed 100, then let timer continue running
    if (flDuration_ret > 100.0)
    {
        //----DEBUG----
        //PrintToChatAll("\x03DT retrieved duration > 100");

        return Plugin_Continue;
    }
	
    //CHECK 2: AFTER ADJUSTED SHOT IS MADE
    //------------------------------------
    //at this point, either a gun was swapped, or
    //the attack time needs to be adjusted
    //also, only change timer if it's the first shot

    //retrieve current timestamp
    PrintToChatAll("");        //插入实现技能修复
    new Float:flTimeStamp_ret = GetEntDataFloat(iEntid,g_iNextActO+8);
    PrintToChatAll("");        //插入实现技能修复
    if (g_flTimeStamp[iCid] < flTimeStamp_ret)
    PrintToChatAll("");        //插入实现技能修复
    {
		
        PrintToChatAll("");        //插入实现技能修复
        g_flTimeStamp[iCid] = flTimeStamp_ret;
        PrintToChatAll("");        //插入实现技能修复
        new Float:flTimeStamp_calc = flTimeStamp_ret - (GetConVarFloat(FindConVar("tongue_hit_delay")) * (1 - g_flDrag_cdmult) );
        PrintToChatAll("");        //插入实现技能修复
        SetEntDataFloat(iEntid, g_iNextActO+8, flTimeStamp_calc, true);
        PrintToChatAll("");        //插入实现技能修复
		
		//PrintToChatAll("tset %f __%f__%f", flTimeStamp_calc, g_flDrag_cdmult, GetConVarFloat(FindConVar("tongue_hit_delay")));
		// 口水CD修正
        if (ability > 0)
		{
			SetEntPropFloat(ability, Prop_Send, "m_duration", g_flDrag_cdmult);
			SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + g_flDrag_cdmult);
		}
    }
    PrintToChatAll("");        //插入实现技能修复
	//PrintToChatAll("tset1122");
    return Plugin_Continue;
}



//=============================
// Inf3: Smoke IT!          专业拖车
//=============================

public Action:SmokeIt_OnTongueGrab(Smoker, Victim)
{
    if (g_iInf3_enable==0
        || g_iSmokeIt_enable==0
        || g_iInf3[Smoker]!=4
        || g_iConfirm[Smoker]!=1)
        return Plugin_Continue;

    //new Smoker = GetClientOfUserId(GetEventInt(event, "userid"));
    if (IsFakeClient(Smoker)) return Plugin_Continue;
    new Handle:pack;
    g_bSmokeItGrabbed[Smoker] = true;
    //new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
    SetEntityMoveType(Smoker, MOVETYPE_ISOMETRIC);
    SetEntDataFloat(Smoker, g_iLaggedMovementO, g_flSmokeItSpeed, true);
    g_hSmokeItTimer[Smoker] = CreateDataTimer(0.2, SmokeItTimerFunction, pack, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT);
    
    SetConVarFloat(FindConVar("tongue_victim_max_speed"), g_flTongueSpeed*g_flTongue_pullmult ,false,false);        //专业拖车叠加舌头拉人速度
    
    WritePackCell(pack, Smoker);
    WritePackCell(pack, Victim);
    //new Float:speed = GetEntDataFloat(Smoker, speedOffset);
    //PrintToChatAll("Speed: %f", speed);
    return Plugin_Continue;
}

public Action:SmokeItTimerFunction(Handle:timer, Handle:pack)
{
    ResetPack(pack);
    new Smoker = ReadPackCell(pack);
    if ((!IsValidPlayer(Smoker, true, false))||(GetClientTeam(Smoker)!=3)||(IsFakeClient(Smoker))||(g_bSmokeItGrabbed[Smoker] = false))
    {
        g_hSmokeItTimer[Smoker] = INVALID_HANDLE;
        return Plugin_Stop;
    }
            
    new Victim = ReadPackCell(pack);
    if ((!IsValidPlayer(Victim, true, false))||(GetClientTeam(Victim)!=2)||(g_bSmokeItGrabbed[Smoker] = false))
    {
        g_hSmokeItTimer[Smoker] = INVALID_HANDLE;
        return Plugin_Stop;
    }
            
    new Float:SmokerPosition[3];
    new Float:VictimPosition[3];
    GetClientAbsOrigin(Smoker,SmokerPosition);
    GetClientAbsOrigin(Victim,VictimPosition);
    new distance = RoundToNearest(GetVectorDistance(SmokerPosition, VictimPosition));
    //PrintToChatAll("Distance: %i", distance);
    if (distance>g_iSmokeItMaxRange)
    {
        SlapPlayer(Smoker, 0, false);
        //PrintToChatAll("\x03BREAK");
    }
    return Plugin_Continue;
}

public Action:SmokeIt_OnTongueRelease(Smoker)
{
    //new Smoker = GetClientOfUserId(GetEventInt(event, "userid"));
    g_bSmokeItGrabbed[Smoker] = false;
    SetEntityMoveType(Smoker, MOVETYPE_CUSTOM);
    SetEntDataFloat(Smoker, g_iLaggedMovementO, 1.0, true);
    if (g_hSmokeItTimer[Smoker] != INVALID_HANDLE)
    {
        KillTimer(g_hSmokeItTimer[Smoker], true);
        g_hSmokeItTimer[Smoker] = INVALID_HANDLE;
    }
    //new Float:speed = GetEntDataFloat(Smoker, speedOffset);
    //PrintToChatAll("Release Event Fired, Speed: %f", speed);
}


//=============================
// Inf4: Body Slam      鬼影之锤
//=============================

//damage function
BodySlam_DamageAdd (iAtt, iVic, iTA, iType, String:stWpn[], iDmgOrig)
{
    //----DEBUG----
    //PrintToChatAll("\x03-hunter claw damage detected, type: \x01%i",GetEventInt(event,"type"));

    if (iTA==3
        && g_iConfirm[iAtt]==1
        && StrEqual(stWpn,"hunter_claw")==true
        && iType==1
        && g_iInf4[iAtt]==1)
    {
        //stop if body slam is disabled
        if (g_iInf4_enable==0
            || g_iBody_enable==0)
            return 1;

        //----DEBUG----
        //PrintToChatAll("\x03body slam check");

        new iMinBound = g_iBody_minbound;

        //body slam only fires if pounce damage
        //was less than 8 (sets minimum pounce damage)
        //or whatever the minimum bound is (was originally 8...)
        if (iDmgOrig<iMinBound)
        {
            //----DEBUG----
            //PrintToChatAll("\x03body slam fire, running checks");

            new iHP=GetEntProp(iVic,Prop_Data,"m_iHealth");

            //if health>8, then run normally
            if (iHP>iMinBound)
            {
                //----DEBUG----
                //PrintToChatAll("\x03iHP>8 condition");

                SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-(iMinBound-iDmgOrig) );
                //PrintHintText(iAtt,"鬼影之锤: %i %t!", iMinBound-iDmgOrig, "BonusDamageText");

                //----DEBUG----
                //PrintToChatAll("\x03-%i bonus damage", (iMinBound-iDmgOrig) );

                return 1;
            }
            //otherwise, we gotta do a bit of work
            //if survivor's health is
            //less than or equal to 8
            else
            {
                //----DEBUG----
                //PrintToChatAll("\x03iHP<8 condition");
                //PrintToChatAll("\x03-iDmgOrig<8 and iHP>1, iDmgOrig: \x01%i\x03, pre-mod iHP: \x01%i",iDmgOrig,iHP);

                new Float:flHPBuff=GetEntDataFloat(iVic,g_iHPBuffO);

                //if victim has health buffer,
                //we need to do some extra work
                //to reduce health buffer as well
                if (flHPBuff>0)
                {
                    //----DEBUG----
                    //PrintToChatAll("\x03-flHPBuff>0 condition, pre-mod HPbuffer: \x01%f", flHPBuff);

                    new iDmgAdd= iMinBound-iDmgOrig ;

                    //if damage add exceeds health,
                    //then we need to take the difference
                    //and apply it to health buffer instead
                    if (iDmgAdd>=iHP)
                    {
                        //----DEBUG----
                        //PrintToChatAll("\x03-iDmgAdd>=iHP condition, pre-mod iDmgAdd: \x01%i",iDmgAdd);

                        //we leave the survivor with 1 health
                        //because the engine will take it away
                        //when it applies the original damage
                        //and we want to avoid strange death behaviour
                        new iDmgCount=iHP-1;
                        iDmgAdd-=iDmgCount;
                        SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-iDmgCount );

                        //if damage add is more than health buffer,
                        //set damage add to health buffer amount
                        new iHPBuff=RoundToFloor(flHPBuff);
                        if (iHPBuff<iDmgAdd) iDmgAdd=iHPBuff;
                        SetEntDataFloat(iVic,g_iHPBuffO, flHPBuff-iDmgAdd ,true);
                        //PrintHintText(iAtt,"Body Slam: %i bonus damage!", iDmgCount+iDmgAdd);        //为什么要注释这这行？忘记了

                        //----DEBUG----
                        //PrintToChatAll("\x03-damage to health: \x01%i\x03, current health: \x01%i",iDmgCount,GetEntProp(iVic,Prop_Data,"m_iHealth"));
                        //PrintToChatAll("\x03-damage to buffer: \x01%i\x03, current buffer: \x01%f",iDmgAdd,GetEntDataFloat(iVic,g_iHPBuffO));

                        return 1;
                    }

                    //if damage add is less than health
                    //remaining, then we simply deal
                    //the extra damage and let the engine
                    //deal with the rest
                    else
                    {
                        //----DEBUG----
                        //PrintToChatAll("\x03-iDmgAdd<iHP condition");

                        SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-iDmgAdd );
                        //PrintHintText(iAtt,"Body Slam: %i bonus damage!", iDmgAdd);        //为什么要注释这这行？忘记了
                        return 1;
                    }
                }

                //otherwise, it's straightforward
                //- just reduce victim's hp
                else
                {
                    //----DEBUG----
                    //PrintToChatAll("\x03no temp hp condition");

                    //if original damage exceeds health,
                    //just skip the rest since there's no
                    //health buffer to worry about
                    if (iDmgOrig>=iHP) return 1;
                    new iDmgAdd= iMinBound-(iHP-iDmgOrig) ;
                    //to prevent strange death behaviour,
                    //reduce damage add to less than that
                    //of remaining health if necessary
                    if (iDmgAdd>=iHP) iDmgAdd=iHP-1;
                    SetEntProp(iVic,Prop_Data,"m_iHealth", iHP-iDmgAdd );
                    //PrintHintText(iAtt,"Body Slam: %i bonus damage!", iDmgAdd);        //为什么要注释这这行？忘记了

                    //----DEBUG----
                    //PrintToChatAll("\x03-iHP<8, %i bonus damage", iDmgAdd );

                    return 1;
                }
            }
        }
        return 1;
    }

    return 0;
}



//=============================
// Inf4: Efficient Killer       夺命杀手
//=============================

//damage function
EfficientKiller_DamageAdd (iAtt,iVic,iTA,iType,String:stWpn[],iDmgOrig)
{
    if (iTA==3
        && g_iConfirm[iAtt]==1
        && StrEqual(stWpn,"hunter_claw")==true
        && iType==128
        && g_iInf4[iAtt]==2)
    {
        //stop if eff.killer is disabled
        if (g_iInf4_enable==0
            || g_iEfficient_enable==0)
            return 1;

        new iDmgAdd = DamageAddRound (iDmgOrig, g_flEfficient_dmgmult);

        if (iDmgAdd==0)
            return 0;

        InfToSurDamageAdd(iVic, iDmgAdd ,iDmgOrig);
        return 1;
    }

    return 0;
}



//=============================
// Inf4: Speed Demon        夺命印记
//=============================

//damage function
SpeedDemon_DamageAdd (iAtt,iVic,iTA,iType,String:stWpn[],iDmgOrig)
{
    if (iTA==3
        && g_iConfirm[iAtt]==1
        && StrEqual(stWpn,"hunter_claw")==true
        && iType==128
        && g_iInf4[iAtt]==4
        && g_iMyDisableTarget[iAtt] == -1)
    {
        //stop if eff.killer is disabled
        if (g_iInf4_enable==0
            || g_iEfficient_enable==0)
            return 1;

        new iDmgAdd = DamageAddRound (iDmgOrig, g_flSpeedDemon_dmgmult);

        if (iDmgAdd==0)
            return 0;

        //----DEBUG----
        //PrintToChatAll("\x05speed demon\x03 damage bonus \x01%i",iDmgAdd);

        InfToSurDamageAdd(iVic, iDmgAdd ,iDmgOrig);
        return 1;
    }

    return 0;
}

SpeedDemon_OnSpawn (iCid)
{
    //stop here if the perk is disabled
    if (g_iSpeedDemon_enable==0
        || g_iInf4_enable==0)
        return 0;

    //check for motion sickness
    if (g_iInf4[iCid]==4
        && g_iConfirm[iCid]==1)
    {
        SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0*g_flSpeedDemon_rate ,true);
        return 1;
    }
    else
        SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0 ,true);

    return 0;
}



//=============================
// Inf4: Grasshopper            黑暗之忍
//=============================

Grass_OnAbilityUse (iCid,String:stAb[])
{
    //stop if grasshopper is disabled
    if (g_iInf4_enable==0
        || g_iGrass_enable==0)
        return 0;

    if (GetClientTeam(iCid)==3
        && g_iInf4[iCid]==3
        && g_iConfirm[iCid]==1)
    {
        //check if it's a pounce/lunge
        if (StrEqual(stAb,"ability_lunge",false)==true)
        {
            CreateTimer(0.1,Grasshopper_DelayedVel,iCid);

            //----DEBUG----
            //PrintToChatAll("\x03grasshopper fired");

            return 1;
        }
    }

    return 0;
}

//delayed velocity change, since the hunter doesn't
//actually start moving until some time after the event
public Action:Grasshopper_DelayedVel (Handle:timer, any:iCid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    decl Float:vecVelocity[3];
    GetEntPropVector(iCid, Prop_Data, "m_vecVelocity", vecVelocity);
    vecVelocity[0] *= g_flGrass_rate;
    vecVelocity[1] *= g_flGrass_rate;
    vecVelocity[2] *= g_flGrass_rate;
    TeleportEntity(iCid, NULL_VECTOR, NULL_VECTOR, vecVelocity);

    return Plugin_Stop;
}


//=============================
// Inf5: Ride Like the Wind     专业车手
//=============================

//for wind to work, must change VICTIM's speed
Wind_OnRideStart (iAtt,iVic)
{
    if (g_iInf5[iAtt]==1
        && g_iConfirm[iAtt]==1
        && g_iInf5_enable==1
        && g_iWind_enable==1)
    {
        SetEntDataFloat(iVic,g_iLaggedMovementO, 1.0*g_flWind_rate ,true);

        //----DEBUG----
        //PrintToChatAll("\x03-wind apply");
    }
    else
        //reset their run speed
        SetEntDataFloat(iVic,g_iLaggedMovementO, 1.0 ,true);


    return 0;
}

Wind_OnRideEnd (iAtt,iVic)
{
    //----DEBUG----
    //PrintToChatAll("\x03-wind remove");

    //reset their run speed
    SetEntDataFloat(iAtt,g_iLaggedMovementO, 1.0 ,true);
    SetEntDataFloat(iVic,g_iLaggedMovementO, 1.0 ,true);
}



//=============================
// Inf5: Cavalier           血气旺盛
//=============================

//set hp after a small delay, to avoid stupid bugs
Cavalier_OnSpawn (iCid)
{
    //stop here if the perk is disabled
    if (g_iCavalier_enable==0
        || g_iInf5_enable==0)
        return 0;

    //check for perk
    if (g_iInf5[iCid]==2
        && g_iConfirm[iCid]==1)
    {
        CreateTimer(0.1, Cavalier_ChangeHP, iCid);
        return 1;
    }
    return 0;
}

public Action:Cavalier_ChangeHP (Handle:timer, any:iCid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false
        || iCid<=0
        || IsClientInGame(iCid)==false
        || GetClientTeam(iCid)!=3)
        return Plugin_Stop;

    SetEntityHealth(iCid, RoundToNearest(GetEntProp(iCid,Prop_Data,"m_iHealth") * (1+g_flCavalier_hpmult) ) );

    new Float:flMaxHP = GetConVarInt(FindConVar("z_jockey_health")) * (1+g_flCavalier_hpmult);
    if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > flMaxHP)
        SetEntProp(iCid,Prop_Data,"m_iHealth", RoundToNearest(flMaxHP) );

    //----DEBUG----
    //PrintToChatAll("\x03juggernaut apply hp boost, health\x01 %i", GetEntProp(iTankid,Prop_Data,"m_iHealth"));

    return Plugin_Stop;
}



//=============================
// Inf5: Frogger        嗜血之鞍
//=============================

Frogger_DamageAdd (iAtt,iVic,iTA,String:stWpn[],iDmgOrig)
{
    if (iTA==3
        && g_iConfirm[iAtt]==1
        && StrEqual(stWpn,"jockey_claw")==true
        && g_iInf5[iAtt]==3)
    {
        //stop if frogger is disabled
        if (g_iInf5_enable==0
            || g_iFrogger_enable==0)
            return 1;

        new iDmgAdd = DamageAddRound (iDmgOrig, g_flFrogger_dmgmult);

        if (iDmgAdd==0)
            return 0;

        //----DEBUG----
        //PrintToChatAll("\x05frogger\x03 damage \x01%i", iDmgAdd);

        InfToSurDamageAdd(iVic, iDmgAdd ,iDmgOrig);
        return 1;
    }

    return 0;
}

Frogger_OnJump (iCid)
{
    //stop if frogger is disabled
    if (g_iInf5_enable==0
        || g_iFrogger_enable==0
        || GetEntData(iCid, g_iClassO) != 5)
        return 0;

    if (GetClientTeam(iCid)==3
        && g_iInf5[iCid]==3
        && g_iConfirm[iCid]==1)
    {
        CreateTimer(0.1,Frogger_DelayedVel,iCid);

        //----DEBUG----
        //PrintToChatAll("\x05frogger\x03 fired");

        return 1;
    }

    return 0;
}

//delayed velocity change, since the hunter doesn't
//actually start moving until some time after the event
public Action:Frogger_DelayedVel (Handle:timer, any:iCid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    decl Float:vecVelocity[3];
    GetEntPropVector(iCid, Prop_Data, "m_vecVelocity", vecVelocity);
    vecVelocity[0] *= g_flFrogger_rate;
    vecVelocity[1] *= g_flFrogger_rate;
    vecVelocity[2] *= g_flFrogger_rate;
    TeleportEntity(iCid, NULL_VECTOR, NULL_VECTOR, vecVelocity);

    return Plugin_Stop;
}



//=============================
// Inf5: Ghost Rider        幽灵幻术
//=============================

Ghost_OnSpawn (iCid)
{
    //stop if frogger is disabled
    if (g_iInf5_enable==0
        || g_iGhost_enable==0)
        return 0;

    if (GetClientTeam(iCid)==3
        && g_iInf5[iCid]==4
        && g_iConfirm[iCid]==1)
    {
        SetEntityRenderMode(iCid, RENDER_TRANSCOLOR);
        SetEntityRenderColor(iCid, 190, 190, 255, g_iGhost_alpha);
        SetEntityHealth(iCid, RoundToNearest(GetEntProp(iCid,Prop_Data,"m_iHealth") + (1907+g_flCavalier_hpmult) ) );        //幽灵幻术叠加血气旺盛
        //----DEBUG----
        //PrintToChatAll("\x03ghost rider fired");

        return 1;
    }

    return 0;
}



//=============================
// Inf6: Twin Spitfire
//=============================

TwinSF_ResetShotCount (iCid)
{
    g_iTwinSFShotCount[iCid]=0;
}

TwinSF_OnSpawn(iCid)
{
    //----DEBUG----
    //PrintToChatAll("\x05twin sf\x03 on spawn");

    //stop if grasshopper is disabled
    if (g_iInf6_enable==0
        || g_iTwinSF_enable==0)
        return 0;

    if (GetClientTeam(iCid)==3
        && g_iInf6[iCid]==1
        && g_iConfirm[iCid]==1)
    {
        //----DEBUG----
        //PrintToChatAll("\x03- creating timer");

        //update the timestamp stored in plugin to prevent confusion for timer function
        if(IsValidEntity(iCid))
        {
            g_flTimeStamp[iCid] = GetEntDataFloat(GetEntDataEnt2(iCid,g_iAbilityO),g_iNextActO+8);
        }
        //reset the shot count
        TwinSF_ResetShotCount(iCid);

        //create the timer to keep changing the spitter's delay
        //CreateTimer(1.0,Timer_TwinSFChecks,iCid,TIMER_REPEAT);
        
        ResetInfectedAbility(iCid, g_flTwinSF_delay);        

        return 1;
    }

    return 0;
}

public Action:Timer_TwinSFChecks (Handle:timer, any:iCid)
{
    //INITIAL CHECKS
    //--------------
    if (IsServerProcessing()==false
        || iCid <= 0
        || IsClientInGame(iCid)==false
        || IsPlayerAlive(iCid)==false
        || GetEntData(iCid, g_iClassO)!=4)
    {
        g_iTwinSFShotCount[iCid]=0;
        KillTimer(timer);
        return Plugin_Stop;
    }

    //if this is the second, unmodified shot (shot count 1), stop
    //if (g_iTwinSFShotCount[iCid]==0)
        //return Plugin_Continue;

    //----DEBUG----
    //PrintToChatAll("\x03- tick");

    //RETRIEVE VARIABLES
    //------------------
    //get the ability ent id
    new iEntid = GetEntDataEnt2(iCid,g_iAbilityO);
    //if the retrieved gun id is -1, then move on
    if (iEntid == -1)
    {
        g_iTwinSFShotCount[iCid]=0;
        KillTimer(timer);
        return Plugin_Stop;
    }
    new ability = GetEntPropEnt(iCid, Prop_Send, "m_customAbility");
    //retrieve the next act time
    new Float:flDuration_ret = GetEntDataFloat(iEntid,g_iNextActO+4);

    //----DEBUG----
    //if (g_iShow==1)
    //    PrintToChatAll("\x03- actsuppress dur \x01 %f\x03 timestamp \x01%f", GetEntDataFloat(iEntid, g_iSuppressO+4), GetEntDataFloat(iEntid, g_iSuppressO+8) );




    //CHECK 1: PAUSE?
    //---------------
    //Valve seems to have a weird way of forcing a
    //pause before the cooldown timer starts: by setting
    //the timers to some arbitrarily high number =/
    //IIRC no cooldown exceeds 100s (highest is 30?) so
    //if any values exceed 100, then let timer continue running
    if (flDuration_ret > 100.0)
    {
        //----DEBUG----
        //PrintToChatAll("\x03DT retrieved duration > 100");

        return Plugin_Continue;
    }



    //CHECK 2: AFTER ADJUSTED SHOT IS MADE
    //------------------------------------
    //at this point, either a gun was swapped, or
    //the attack time needs to be adjusted
    //also, only change timer if it's the first shot

    //retrieve current timestamp
    new Float:flTimeStamp_ret = GetEntDataFloat(iEntid,g_iNextActO+8);

    if (g_flTimeStamp[iCid] < flTimeStamp_ret)
    {
        //----DEBUG----
        //PrintToChatAll("\x05TwinSF:\x03 after adjusted shot\n-pre, client \x01%i\x03; entid \x01%i\x03; enginetime\x01 %f\x03; nextactivation: dur \x01 %f\x03 timestamp \x01%f",iCid,iEntid,GetGameTime(),GetEntDataFloat(iEntid, g_iNextActO+4), GetEntDataFloat(iEntid, g_iNextActO+8) );

        //update the timestamp stored in plugin
        g_flTimeStamp[iCid] = flTimeStamp_ret;
        //increase the shot count
        g_iTwinSFShotCount[iCid]++;

        //----DEBUG----
        //PrintToChatAll("\x05TwinSF\x03 shot count \x01%i", g_iTwinSFShotCount[iCid]);

        //check how many shots have been made
        if (g_iTwinSFShotCount[iCid] >= 6)            //口水多重喷射次数A
        {
            //----DEBUG----
            //PrintToChatAll("\x05TwinSF\x03 shot count >=3, setting to x-2");

            //reset shot count if more than 3 shots have been made
            g_iTwinSFShotCount[iCid] -= 5;            //口水多重喷射次数B
        }
        else if (g_iTwinSFShotCount[iCid] == 5)        //口水多重喷射次数C
        {
            //----DEBUG----
            //PrintToChatAll("\x05TwinSF\x03 shot count ==2, continuing");

            //don't do anything if one shot has been made
            return Plugin_Continue;
        }

        //this calculates the time that the player theoretically
        //should have used his ability in order to use it
        //with the shortened cooldown
        //FOR EXAMPLE:
        //vomit, normal cooldown 30s, desired cooldown 6s
        //player uses it at T = 1:30
        //normally, game predicts it to be ready at T + 30s
        //so if we modify T to 1:06, it will be ready at 1:36
        //which is 6s after the player used the ability
        new Float:flTimeStamp_calc = flTimeStamp_ret - (GetConVarFloat(FindConVar("z_spit_interval")) - g_flTwinSF_delay);
        SetEntDataFloat(iEntid, g_iNextActO+8, flTimeStamp_calc, true);

        // CD修正
        if (ability > 0)
		{
			SetEntPropFloat(ability, Prop_Send, "m_duration", g_flTwinSF_delay);
			SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + g_flTwinSF_delay);
		}
        //----DEBUG----
        //PrintToChatAll("\x03-post, nextactivation dur \x01 %f\x03 timestamp \x01%f", GetEntDataFloat(iEntid, g_iNextActO+4), GetEntDataFloat(iEntid, g_iNextActO+8) );
    }

    return Plugin_Continue;
}



//=============================
// Inf6: Mega Adhesive          粘性毒液
//=============================

MegaAd_SlowEffect (iAtt, iVic, String:stWpn[])
{
    if (g_iConfirm[iAtt]==1
        && StrEqual(stWpn,"insect_swarm")==true
        && g_iInf6[iAtt]==2)
    {
        //stop if perk is disabled
        /*if (g_iInf6_enable==0
            || g_iMegaAd_enable==0)
            return 1;*/

        //----DEBUG----
        //PrintToChatAll("\x05megaadhesive\x03 fire, client \x01%i\x03, pre-mod amount \x01%i", iVic, g_iMegaAdCount[iVic]);

        if (g_iMegaAdCount[iVic] <= 0)
        {
            g_iMegaAdCount[iVic] = 10;

            //check if another SI is disabling the survivor
            new iDisabler = g_iMyDisabler[iVic];
            if (iDisabler == -1)
                SetEntDataFloat(iVic,g_iLaggedMovementO, g_flMegaAd_slow ,true);
            else
            {
                //check if disabler is valid
                //if not, then just apply normal effects
                if (iDisabler == 0
                    || IsValidEntity(iDisabler)==false
                    || IsValidPlayer(iVic)==false)
                    SetEntDataFloat(iVic,g_iLaggedMovementO, g_flMegaAd_slow ,true);
                //otherwise if it's valid, then check the class
                //don't apply slow for jockeys or smokers
                else
                {
                    //1 = smoker, 5 = jockey
                    new iClass = GetEntData(iDisabler, g_iClassO);
                    if (iClass != 1
                        && iClass != 5)
                        SetEntDataFloat(iVic,g_iLaggedMovementO, g_flMegaAd_slow ,true);
                }
            }
        }
        else
            g_iMegaAdCount[iVic]++;

        if (g_hMegaAdTimer[iVic] == INVALID_HANDLE)
            g_hMegaAdTimer[iVic] = CreateTimer(0.3, MegaAd_Timer, iVic, TIMER_REPEAT);

        return 1;
    }
    return 0;
}

public Action:MegaAd_Timer (Handle:timer, any:iVic)
{
    if (IsServerProcessing()==false
        || IsValidEntity(iVic)==false
        || IsValidPlayer(iVic)==false)
    {
        KillTimer(timer);
        g_hMegaAdTimer[iVic] = INVALID_HANDLE;
        return Plugin_Stop;
    }

    g_iMegaAdCount[iVic]--;

    //----DEBUG----
    //PrintToChatAll("\x03- tick, client \x01%i\x03 amount \x01%i", iVic, g_iMegaAdCount[iVic] );

    if (g_iMegaAdCount[iVic] > 0)
    {
        //SetEntDataFloat(iVic,g_iLaggedMovementO, g_flMegaAd_slow ,true);

        //check if another SI is disabling the survivor
        new iDisabler = g_iMyDisabler[iVic];
        if (iDisabler == -1)
            SetEntDataFloat(iVic,g_iLaggedMovementO, g_flMegaAd_slow ,true);
        else
        {
            //check if disabler is valid
            //if not, then just apply normal effects
            if (IsValidEntity(iDisabler)==false
                || IsValidPlayer(iVic)==false)
                SetEntDataFloat(iVic,g_iLaggedMovementO, g_flMegaAd_slow ,true);
            //otherwise if it's valid, then check the class
            //don't apply slow for jockeys or smokers
            else
            {
                //1 = smoker, 5 = jockey
                new iClass = GetEntData(iDisabler, g_iClassO);
                if (iClass != 1
                    && iClass != 5)
                    SetEntDataFloat(iVic,g_iLaggedMovementO, g_flMegaAd_slow ,true);
            }
        }

        return Plugin_Continue;
    }
    else
    {
        //----DEBUG----
        //PrintToChatAll("\x03- duration over, killing timer");

        g_iMegaAdCount[iVic] = 0;
        SetEntDataFloat(iVic,g_iLaggedMovementO, 1.0 ,true);

        //check if survivor has extra run speed
        Extreme_Rebuild();

        KillTimer(timer);
        g_hMegaAdTimer[iVic] = INVALID_HANDLE;
        return Plugin_Stop;
    }
}



//=============================
// Inf7: Scattering Ram         战神之力
//=============================

Scatter_OnImpact (iAtt,iVic)
{
    //stop if disabled
    if (g_iInf7_enable==0
        || g_iScatter_enable==0)
        return 0;

    if (GetClientTeam(iAtt)==3
        && g_iInf7[iAtt]==1
        && g_iConfirm[iAtt]==1)
    {
		PrintToChatAll("【 %N 】使用战神牛攻击【 %N 】", iAtt, iVic);
        CreateTimer(0.1,Timer_ScatterForce,iVic);

		
        //----DEBUG----
        //PrintToChatAll("\x05Scatter \x03fired");

        return 1;
    }

    return 0;
}

Scatter_OnSpawn (iCid)
{
    //stop here if the perk is disabled
    if (g_iScatter_enable==0
        || g_iInf7_enable==0)
        return 0;

    //check for perk
    if (g_iInf7[iCid]==1
        && g_iConfirm[iCid]==1)
    {
        CreateTimer(0.1, Scatter_ChangeHP, iCid);
        return 1;
    }
    return 0;
}

public Action:Timer_ScatterForce (Handle:timer, any:iVic)
{
    KillTimer(timer);

    if (IsServerProcessing()==false)
        return Plugin_Stop;

	PrintToChatAll("【 %N 】受到战神牛攻击坐标变换 %f", iVic, g_flScatter_force);
    decl Float:vecVelocity[3];
    GetEntPropVector(iVic, Prop_Data, "m_vecVelocity", vecVelocity);
    vecVelocity[0] *= g_flScatter_force;
    vecVelocity[1] *= g_flScatter_force;
    vecVelocity[2] *= g_flScatter_force;
    TeleportEntity(iVic, NULL_VECTOR, NULL_VECTOR, vecVelocity);

    return Plugin_Stop;
}

public Action:Scatter_ChangeHP (Handle:timer, any:iCid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false
        || iCid<=0
        || IsClientInGame(iCid)==false
        || GetClientTeam(iCid)!=3)
        return Plugin_Stop;

    SetEntityHealth(iCid, RoundToNearest(GetEntProp(iCid,Prop_Data,"m_iHealth") * (1+g_flScatter_hpmult) ) );

    new Float:flMaxHP = GetConVarInt(FindConVar("z_charger_health")) * (1+g_flScatter_hpmult);
    if ( GetEntProp(iCid,Prop_Data,"m_iHealth") > flMaxHP)
        SetEntProp(iCid,Prop_Data,"m_iHealth", RoundToNearest(flMaxHP) );

    //----DEBUG----
    //PrintToChatAll("\x03juggernaut apply hp boost, health\x01 %i", GetEntProp(iTankid,Prop_Data,"m_iHealth"));

    return Plugin_Stop;
}



//=============================
// Inf7: Speeding Bullet            急速冲击
//=============================

Bullet_OnAbilityUse (iCid,String:stAb[])
{
    //stop if frogger is disabled
    if (g_iInf7_enable==0
        || g_iBullet_enable==0)
        return 0;

    if (GetClientTeam(iCid)==3
        && g_iInf7[iCid]==2
        && g_iConfirm[iCid]==1)
    {
        //check if it's a pounce/lunge
        if (StrEqual(stAb,"ability_charge",false)==true)
        {
            SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0*g_flBullet_rate ,true);

            //----DEBUG----
            //PrintToChatAll("\x03speeding bullet fired");

            return 1;
        }
    }

    return 0;
}



//=============================
// Inf2: Tank Perks         tank技能源码
//=============================


//PRIMARY TANK FUNCTION        ----------------------
//primary function for handling tank spawns         // 创建tank事件
public Event_Tank_Spawn (Handle:event, const String:name[], bool:dontBroadcast)
{
    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (iCid==0
        || GetClientTeam(iCid)!=3)
        return;
    /* Reset flags */
    clientlosscount[iCid] = 0;

    //----DEBUG----
    //PrintToChatAll("\x03Tank Spawn detected, client \x01%i\x03,g_iTank \x01%i", iCid, g_iTank);

    //reset their run speed
    //from martial artist or speed demon    重置奔跑速度（从疯狂暴走或者夺命印记创建来的tank）
    SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0 ,true);

    //stop if tank perks are disallowed
    if (g_iInf2_enable==0)
        return;

    //start a check if it's a bot       机器tank
    if (IsFakeClient(iCid)==true)
    {
        g_iTankBotTicks=0;
        CreateTimer(2.5,Timer_TankBot,iCid,TIMER_REPEAT);
    }
    else
    {
        CreateTimer(1.0,Timer_Tank_ApplyPerk,iCid);     // 玩家tank的技能设定
    }
    // 检测tank丢控制事件，创建后10秒运行执行
    if (iCid > 0 && iCid < MaxClients && IsClientInGame(iCid) && IsValidEntity(iCid) && !IsFakeClient(iCid))
    {
        CreateTimer(10.0, CheckFrustration, iCid);
    }
}
// tank技能应用
public Action:Timer_Tank_ApplyPerk (Handle:timer, any:iCid)
{
    KillTimer(timer);
    Tank_ApplyPerk(iCid);
}
// tank技能修正
Tank_ApplyPerk (any:iCid)
{
    //why apply tank perks to non-infected? 判断控制tank的玩家为感染者而非幸存者
    if (IsClientInGame(iCid)==false
        || GetClientTeam(iCid)!=3)
        return;
    
    //and make sure we're dealing with a tank       // 确定当前特感为一只tank
    new String:st_class[32];
    GetClientModel(iCid,st_class,32);
    if (StrContains(st_class,"hulk",false) == -1)
        return;

    //----DEBUG----
    //PrintToChatAll("\x03applying perks; tank detected, model: \x01%s",st_class);

    //first battery of tests for perks 1-4 (not double trouble)     // 为1-4的坦克模式设置（非多tank模式）
    if (g_iTank<2  // 当前为没tank，或者无技能tank的模式
        && g_iConfirm[iCid]==1  // 玩家query
        && g_iInf2[iCid]<5)     // 玩家所选tank技能为非第五种模式（3F为分身）
    {
        switch (g_iInf2[iCid])
        {
        //check for adrenal glands
        case 1:// 第一种tank，疯狂暴走
            {
                //----DEBUG----
                //PrintToChatAll("\x03applying adrenal glands");

                g_iTank=1; // 疯狂暴走为无技能tank

                //stop if adrenal glands is disabled
                if (g_iAdrenal_enable==0)
                    return;

                Adrenal_Rebuild();      // 疯狂暴走技能实现

                if (IsFakeClient(iCid)==false)
                    PrintHintText(iCid,"疯狂暴走: %t", "AdrenalGlandsHint");
                ClientCommand(iCid, "sm_buy");        //修复8分健康BUG的利用
                return ;
            }
        //check for juggernaut perk     第二种为魔王重生
        case 2:
            {
                //at least tell plugin that there's a tank
                g_iTank=1;

                //stop if juggernaut is disabled
                if (g_iJuggernaut_enable==0)
                    return;

                //----DEBUG----
                //PrintToChatAll("\x03applying juggernaut");

                CreateTimer(0.1,Juggernaut_ChangeHP,iCid);      // 增加玩家tank血量

                //if it's gotten this far, juggernaut's
                //about to get applied so we tell plugin the news
                g_iTank=2;              // 有技能tank模式

                if (IsFakeClient(iCid)==false)
                    PrintHintText(iCid,"魔王重生技能: %t", "JuggernautHint");
                ClientCommand(iCid, "sm_buy");        //修复8分健康BUG的利用
                return ;
            }

        //check for metabolic boost             第三种神行百变tank
        case 3:
            {
                //----DEBUG----
                //PrintToChatAll("\x03applying metabolic boost");

                g_iTank=1;          // 无特殊技能tank
                // 设置移动速度
                SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0*g_flMetabolic_speedmult ,true);
                if (IsFakeClient(iCid)==false)
                    PrintHintText(iCid,"坦克神行百变技: %t", "MetabolicHint");
                ClientCommand(iCid, "sm_buy");        //修复8分健康BUG的利用
                return ;
            }

        //check for storm caller            第四种王者召唤tank
        case 4:
            {
                g_iTank=1;          // 无技能tank模式

                //stop if storm caller is disabled
                if (g_iStorm_enable==0)
                    return;

                //----DEBUG----
                //PrintToChatAll("\x03applying storm caller");
                CreateTimer(4.0, SpawnWitch1, iCid);        //跳到下面的SpawnWitch1
                if (IsFakeClient(iCid)==false)
                    PrintHintText(iCid,"王者召唤: %t", "StormCallerHint");
                ClientCommand(iCid, "sm_buy");        //修复8分健康BUG的利用
                return ;
            }
        }
    }

    //check for double trouble activation;
    //must have perk confirmed (g_iConfirm==1)
    //and double trouble must not be in effect (g_iTank!=3,4)
    else if (g_iInf2[iCid]==5 // 出现当前玩家选择为第五种分身tank
        && g_iTank<3            // 当前tank模式为小于3的无tank，或者有一只tank
        && g_iConfirm[iCid]==1)
    {
        g_iTank=3;          // tank数量增加，当前tank模式为多坦克模式
        g_iTank_MainId=iCid;        // 当前玩家为主tank

        //stop if double trouble is disabled
        if (g_iDouble_enable==0)
            return;

        //recount the number of tanks left  重新计算当前tank数量
        g_iTankCount=0;
        for (new iI=1 ; iI<=MaxClients ; iI++)
        {
            if (IsClientInGame(iI)==true
                && IsPlayerAlive(iI)==true
                && GetClientTeam(iI)==3) // 玩家为感染者
            {
                GetClientModel(iI,st_class,32); // 获得玩家特感类别
                if (StrContains(st_class,"hulk",false) != -1) // 当前类别为tank
                    g_iTankCount++;

                //----DEBUG----
                //PrintToChatAll("\x03-counting \x01%i",iI);
            }
        }
        
        //----DEBUG----
        //PrintToChatAll("\x03double trouble 1st tank apply");

        CreateTimer(1.0,DoubleTrouble_ChangeHP,iCid);
        CreateTimer(1.0,DoubleTrouble_SpawnTank,iCid);

        CreateTimer(3.0,DoubleTrouble_FrustrationTimer,iCid,TIMER_REPEAT);

        if (IsFakeClient(iCid)==false)
            PrintHintText(iCid,"分身术: %t", "DoubleTroubleHint1");
        ClientCommand(iCid, "sm_buy");        //修复8分健康BUG的利用
        return ;
    }

    //if double trouble is activated (g_iTank==3)
    //subsequent tanks will have reduced hp
    else if (g_iTank==3)
    {
        //stop if double trouble is disabled
        if (g_iDouble_enable==0)
            return;

        //recount the number of tanks left
        g_iTankCount=0;
        for (new iI=1 ; iI<=MaxClients ; iI++)
        {
            if (IsClientInGame(iI)==true
                && IsPlayerAlive(iI)==true
                && GetClientTeam(iI)==3)
            {
                GetClientModel(iI,st_class,32);
                if (StrContains(st_class,"hulk",false) != -1)
                    g_iTankCount++;

                //----DEBUG----
                //PrintToChatAll("\x03-counting \x01%i",iI);
            }
        }

        //----DEBUG----
        //PrintToChatAll("\x03double trouble 2nd+ tank apply");

        if (IsFakeClient(iCid)==false)
            PrintHintText(iCid,"%t", "DoubleTroubleHint2");
        CreateTimer(0.1,DoubleTrouble_ChangeHP,iCid);

        //CreateTimer(3.0,DoubleTrouble_FrustrationTimer,iCid,TIMER_REPEAT);

        decl Float:vecOrigin[3];
        if(IsValidPlayer(g_iTank_MainId))
        {        
            GetClientAbsOrigin(g_iTank_MainId,vecOrigin);
            TeleportEntity(iCid,vecOrigin,NULL_VECTOR,NULL_VECTOR);
        }

        return ;
    }

    //if frustrated double trouble tank is being passed, do nothing
    else if (g_iTank==4)
    {
        //----DEBUG----
        //PrintToChatAll("\x03double trouble, frustration pass (no perks granted)");

        g_iTank=3;
        return ;
    }

    //if none of the special perks apply, just tell plugin that there's a tank
    else
    {
        //----DEBUG----
        //PrintToChatAll("\x03nothing applied, setting g_iTank to 1");

        g_iTank=1;
        return ;
    }
}
public Action:SpawnWitch1(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);

    for (new iI=0 ; iI<=g_iStorm_mobcount ; iI++)
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch2, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch2(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch3, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch3(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch4, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch4(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch5, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch5(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch6, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch6(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch7, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch7(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch8, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch8(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch9, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch9(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch10, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch10(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch11, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch11(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch12, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch12(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"");
        CreateTimer(1.0, SpawnWitch13, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch13(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"z_spawn_old witch");
        CreateTimer(1.0, SpawnWitch14, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch14(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"z_spawn_old witch");
        CreateTimer(1.0, SpawnWitch15, iCid);
    }
    SetCommandFlags("z_spawn_old", iflags);
}
public Action:SpawnWitch15(Handle:timer, any:iCid)
{
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);
    {
        FakeClientCommand(iCid,"z_spawn_old witch");
    }
    SetCommandFlags("z_spawn_old", iflags);
}


//END PRIMARY TANK FUNCTION ----------------------

//timer functions to change tank's hp
//since doing it immediately doesn't seem to work =/ 疯狂暴走tank血量修改
public Action:Juggernaut_ChangeHP (Handle:timer, any:iTankid)
{
    KillTimer(timer);
    if (IsServerProcessing()==false
        || iTankid==0
        || IsClientInGame(iTankid)==false
        || GetClientTeam(iTankid)!=3)
        return Plugin_Stop;

    SetEntityHealth(iTankid, GetEntProp(iTankid,Prop_Data,"m_iHealth") + g_iJuggernaut_hp );

    //----DEBUG----
    //PrintToChatAll("\x03juggernaut apply hp boost, health\x01 %i", GetEntProp(iTankid,Prop_Data,"m_iHealth"));

    return Plugin_Stop;
}
public Action:DoubleTrouble_ChangeHP (Handle:timer, any:iTankid)    // 血量修改
{
    KillTimer(timer);
    if (IsServerProcessing()==false
        || iTankid==0
        || IsClientInGame(iTankid)==false
        || GetClientTeam(iTankid)!=3)
        return Plugin_Stop;

    SetEntityHealth(iTankid, RoundToCeil(GetEntProp(iTankid,Prop_Data,"m_iHealth")*g_flDouble_hpmult) );

    //----DEBUG----
    //PrintToChatAll("\x03double trouble apply hp reduction, health \x01%i", GetEntProp(iTankid,Prop_Data,"m_iHealth"));

    return Plugin_Stop;
}

public Action:DoubleTrouble_SpawnTank (Handle:timer, any:iCid)      // 产生多tank
{
    KillTimer(timer);
    if (IsServerProcessing()==false)
        return Plugin_Stop;

    //strip flags
    new iflags=GetCommandFlags("z_spawn_old");
    SetCommandFlags("z_spawn_old", iflags & ~FCVAR_CHEAT);

    decl iSpawner;
    new iCount = 0;
    new iReg[32] = 0;
    //before we can spawn the tank, need to find a suitable players
    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        if (IsClientInGame(iI)==true
            && IsFakeClient(iI)==false
            && GetClientTeam(iI)==3
            && iI != iCid
            //check if client is either dead or a ghost
            && ( GetClientHealth(iI)<=1 || GetEntData(iI,g_iIsGhostO)!=0 ) )
        {
            iCount++;
            iReg[iCount]=iI;
        }
    }

    //check if any players were available
    if (iCount==0)
    {
        iSpawner = CreateFakeClient("perkmod - bot tank spawner");
        CreateTimer(5.0, DoubleTrouble_KickBotSpawner, iSpawner, TIMER_REPEAT);
    }
    else
        iSpawner = iReg[ GetRandomInt(1,iCount) ];

    //----DEBUG----
    //PrintToChatAll("\x05double trouble\x03 spawner id \x01%i", iSpawner);

    //spawn the tank and reset flags
    FakeClientCommand(iSpawner,"z_spawn_old tank");
    FakeClientCommand(iSpawner,"z_spawn_old tank");        //附加分身TANK数量
    FakeClientCommand(iSpawner,"z_spawn_old tank");        //附加分身TANK数量
    FakeClientCommand(iSpawner,"z_spawn_old tank");        //附加分身TANK数量
    SetCommandFlags("z_spawn_old", iflags);

    //----DEBUG----
    //PrintToChatAll("\x03double trouble attempting to spawn 2nd tank");

    return Plugin_Stop;
}

public Action:DoubleTrouble_KickBotSpawner (Handle:timer, any:iSpawner) // 剔除机器tank
{
    if ((IsServerProcessing()==false
        && IsFakeClient(iSpawner)==true)
        || (IsClientInGame(iSpawner)==true
        && IsClientInKickQueue(iSpawner)==false
        && IsPlayerAlive(iSpawner)==false
        && IsFakeClient(iSpawner)==true) )
    {
        KickClient(iSpawner);
        KillTimer(timer);
        return Plugin_Stop;
    }
    return Plugin_Continue;
}

Adrenal_Rebuild ()          // 疯狂暴走技能源码
{
    //clears all DT-related vars
    Adrenal_Clear();

    //if the server's not running or
    //is in the middle of loading, stop
    if (IsServerProcessing()==false)
        return;

    //----DEBUG----
    //PrintToChatAll("\x03martial artist rebuilding registry");

    if (g_iInf2_enable==0)
        return;

    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        if (IsClientInGame(iI)==true
            && IsPlayerAlive(iI)==true
            && GetClientTeam(iI)==3
            && g_iInf2[iI]==1
            && g_iConfirm[iI]==1)
        {
            g_iAdrenalRegisterCount++;
            g_iAdrenalRegisterIndex[g_iAdrenalRegisterCount]=iI;

            //----DEBUG----
            //PrintToChatAll("\x03-registering \x01%i",iI);
        }
    }
}

Adrenal_Clear ()
{
    g_iAdrenalRegisterCount=0;
    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        g_iAdrenalRegisterIndex[iI]= -1;
    }

    //----DEBUG----
    //PrintToChatAll("\x03cleared");
}

Adrenal_OnGameFrame()
{
    /*    //以下是疯狂暴走增加随机学到机率
    new mod2 = GetRandomInt(0, 0);    //疯狂暴走 有多少几率生效    //0，0是绝对有效。
    switch(mod2)
        {
        case 0:
            {
                //or if no one has DT, don't bother either
                if (g_iAdrenalRegisterCount==0
                    || g_iTank<=0)
                    return 0;

                decl iCid;
                //this tracks the player's ability id
                decl iEntid;
                //this tracks the calculated next attack
                decl Float:flTimeStamp_calc;
                //this, retrieved next attack
                decl Float:flDuration_ret;
                //this, on the other hand, tracks the current next attack
                decl Float:flTimeStamp_ret;

                for (new iI=1; iI<=g_iAdrenalRegisterCount; iI++)
                {
                    iCid = g_iAdrenalRegisterIndex[iI];
                    if (iCid <= 0) return 0;
                    iEntid = GetEntDataEnt2(iCid,g_iActiveWO);
                    if (iEntid == -1) continue;
                    flDuration_ret = GetEntDataFloat(iEntid,g_iAttackTimerO+4);
                    flTimeStamp_ret = GetEntDataFloat(iEntid,g_iAttackTimerO+8);
                    //SetEntData(iCid, g_iFrustrationO, 0);    //疯狂暴走无限CD
                    if (g_flAdrenalTimeStamp[iCid] < flTimeStamp_ret)
                    {
                        g_flAdrenalTimeStamp[iCid] = flTimeStamp_ret;
                        flTimeStamp_calc = flTimeStamp_ret - (flDuration_ret * (1 - g_flAdrenal_punchcdmult) );
                        SetEntDataFloat(iEntid, g_iAttackTimerO+8, flTimeStamp_calc, true);
                        SetEntDataFloat(iEntid, g_iNextPAttO, flTimeStamp_calc, true);
                        continue;

                    }
                    iEntid = GetEntDataEnt2(iCid,g_iAbilityO);
                    if (iEntid == -1) continue;
                    flDuration_ret = GetEntDataFloat(iEntid,g_iNextActO+4);
                    if (flDuration_ret > 100.0)
                    {
                        continue;
                    }
                    flTimeStamp_ret = GetEntDataFloat(iEntid,g_iNextActO+8);

                    if (g_flTimeStamp[iCid] < flTimeStamp_ret)
                    {
                        g_flTimeStamp[iCid] = flTimeStamp_ret;
                        flTimeStamp_calc = flTimeStamp_ret - (flDuration_ret * g_flAdrenal_throwcdmult);
                        SetEntDataFloat(iEntid, g_iNextActO+8, flTimeStamp_calc, true);
                        //PrintToChatAll("\x03-post, nextactivation dur \x01 %f\x03 timestamp \x01%f", GetEntDataFloat(iEntid, g_iNextActO+4), GetEntDataFloat(iEntid, g_iNextActO+8) );
                    }
                }
            }
        case 1:
            {
                    
            }
        }
    */    //以上是疯狂暴走增加随机学到机率
    
    
    
    //or if no one has DT, don't bother either
    if (g_iAdrenalRegisterCount==0 || g_iTank<=0)
        return 0;

    decl iCid;
    //this tracks the player's ability id
    decl iEntid;
    //this tracks the calculated next attack
    decl Float:flTimeStamp_calc;
    //this, retrieved next attack
    decl Float:flDuration_ret;
    //this, on the other hand, tracks the current next attack
    decl Float:flTimeStamp_ret;

    for (new iI=1; iI<=g_iAdrenalRegisterCount; iI++)
    {
        //PUNCH MOD
        //---------

        //PRE-CHECKS 1: RETRIEVE VARS
        //---------------------------

        iCid = g_iAdrenalRegisterIndex[iI];
        //stop on this client
        //when the next client id is null
        if (iCid <= 0) return 0;

        //we have to adjust numbers on the gun, not the player
        //so we get the active weapon id here
        iEntid = GetEntDataEnt2(iCid,g_iActiveWO);
        //if the retrieved gun id is -1, then...
        //wtf mate? just move on
        if (iEntid == -1) continue;
        //new ability = GetEntPropEnt(iCid, Prop_Send, "m_customAbility");

        flDuration_ret = GetEntDataFloat(iEntid,g_iAttackTimerO+4);
        flTimeStamp_ret = GetEntDataFloat(iEntid,g_iAttackTimerO+8);

        //reset frustration
        //SetEntData(iCid, g_iFrustrationO, 0);    //疯狂暴走无限CD


        //CHECK 1: AFTER ADJUSTED ATT IS MADE
        //------------------------------------
        //at this point, either a gun was swapped, or
        //the attack time needs to be adjusted
        //checks: stored gun id same as retrieved gun id,
        // and retrieved next attack time is after stored value
        //actions: adjusts next attack time
        if (g_flAdrenalTimeStamp[iCid] < flTimeStamp_ret)
        {
            //----DEBUG----
            //PrintToChatAll("\x05Adrenal\x03 after adjusted shot\n-pre, client \x01%i\x03; entid \x01%i\x03; enginetime\x01 %f\x03; nextactivation: dur \x01 %f\x03 timestamp \x01%f",iCid,iEntid,GetGameTime(),GetEntDataFloat(iEntid, g_iAttackTimerO+4), GetEntDataFloat(iEntid, g_iAttackTimerO+8) );

            //update the timestamp stored in plugin
            g_flAdrenalTimeStamp[iCid] = flTimeStamp_ret;

            //this calculates the time that the player theoretically
            //should have used his ability in order to use it
            //with the shortened cooldown
            //FOR EXAMPLE:
            //vomit, normal cooldown 30s, desired cooldown 6s
            //player uses it at T = 1:30
            //normally, game predicts it to be ready at T + 30s
            //so if we modify T to 1:06, it will be ready at 1:36
            //which is 6s after the player used the ability
            flTimeStamp_calc = flTimeStamp_ret - (flDuration_ret * (1 - g_flAdrenal_punchcdmult) );
            SetEntDataFloat(iEntid, g_iAttackTimerO+8, flTimeStamp_calc, true);
            SetEntDataFloat(iEntid, g_iNextPAttO, flTimeStamp_calc, true);
            // tank CD修正2014-4-14
/*             if (ability > 0)
            {
                SetEntPropFloat(ability, Prop_Send, "m_duration", g_flAdrenal_punchcdmult);
                SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + g_flAdrenal_punchcdmult);
            } */
            //SetEntDataFloat(iEntid, g_iNextSAttO, flTimeStamp_calc, true);

            //similar logic to above, but this change is necessary
            //so that the little cooldown gui is shown properly
            //SetEntDataFloat(iEntid, g_iNextActO+4, flNextAct_ret * (1 + 0.2) , true);

            //----DEBUG----
            //PrintToChatAll("\x03-post, nextactivation dur \x01 %f\x03 timestamp \x01%f", GetEntDataFloat(iEntid, g_iAttackTimerO+4), GetEntDataFloat(iEntid, g_iAttackTimerO+8) );

            continue;
        }



        //THROW MOD
        //---------

        //RETRIEVE VARIABLES
        //------------------
        //get the ability ent id
        iEntid = GetEntDataEnt2(iCid,g_iAbilityO);
        //if the retrieved gun id is -1, then move on
        if (iEntid == -1) continue;
        //retrieve the next act time
        flDuration_ret = GetEntDataFloat(iEntid,g_iNextActO+4);

        //----DEBUG----
        //if (g_iShow==1)
        //    PrintToChatAll("\x03- actsuppress dur \x01 %f\x03 timestamp \x01%f", GetEntDataFloat(iEntid, g_iSuppressO+4), GetEntDataFloat(iEntid, g_iSuppressO+8) );


        //CHECK 1: PAUSE?
        //---------------
        //Valve seems to have a weird way of forcing a
        //pause before the cooldown timer starts: by setting
        //the timers to some arbitrarily high number =/
        //IIRC no cooldown exceeds 100s (highest is 30?) so
        //if any values exceed 100, then let timer continue running
        if (flDuration_ret > 100.0)
        {
            //----DEBUG----
            //PrintToChatAll("\x03DT retrieved duration > 100");

            continue;
        }


        //CHECK 2: AFTER ADJUSTED SHOT IS MADE
        //------------------------------------
        //at this point, either a gun was swapped, or
        //the attack time needs to be adjusted
        //also, only change timer if it's the first shot

        //retrieve current timestamp
        flTimeStamp_ret = GetEntDataFloat(iEntid,g_iNextActO+8);

        if (g_flTimeStamp[iCid] < flTimeStamp_ret)
        {
            //----DEBUG----
            //PrintToChatAll("\x05TwinSF:\x03 after adjusted shot\n-pre, client \x01%i\x03; entid \x01%i\x03; enginetime\x01 %f\x03; nextactivation: dur \x01 %f\x03 timestamp \x01%f",iCid,iEntid,GetGameTime(),GetEntDataFloat(iEntid, g_iNextActO+4), GetEntDataFloat(iEntid, g_iNextActO+8) );

            //update the timestamp stored in plugin
            g_flTimeStamp[iCid] = flTimeStamp_ret;

            //this calculates the time that the player theoretically
            //should have used his ability in order to use it
            //with the shortened cooldown
            //FOR EXAMPLE:
            //vomit, normal cooldown 30s, desired cooldown 6s
            //player uses it at T = 1:30
            //normally, game predicts it to be ready at T + 30s
            //so if we modify T to 1:06, it will be ready at 1:36
            //which is 6s after the player used the ability

            //----DEBUG----
            //PrintToChatAll("\x03-calc, flTimeStamp_ret \x01%f\x03 flDuration_ret \x01%f\x03 g_flRockInterval \x01%f", flTimeStamp_ret, flDuration_ret, g_flAdrenal_throwcdmult );

            flTimeStamp_calc = flTimeStamp_ret - (flDuration_ret * g_flAdrenal_throwcdmult);
            SetEntDataFloat(iEntid, g_iNextActO+8, flTimeStamp_calc, true);

            //----DEBUG----
            //PrintToChatAll("\x03-post, nextactivation dur \x01 %f\x03 timestamp \x01%f", GetEntDataFloat(iEntid, g_iNextActO+4), GetEntDataFloat(iEntid, g_iNextActO+8) );
        }
    }

    return 0;
}

//resets frustration for double trouble tanks
//which is a band-aid solution =P for disappearing
//tanks whenever one becomes frustrated when there's
//two or more active tanks                                          多tank的时候，丢失控制权
public Action:DoubleTrouble_FrustrationTimer (Handle:timer, any:iCid)
{
    if (IsServerProcessing()==false)
    {
        KillTimer(timer);
        return Plugin_Stop;
    }

    //----DEBUG----
    /*
    PrintToChatAll("\x03tank frustration reset begin");
    if (IsServerProcessing()==false)
        PrintToChatAll("\x03- not processing");
    if (IsClientInGame(iCid)==false)
        PrintToChatAll("\x03- not ingame");
    if (IsFakeClient(iCid)==true)
        PrintToChatAll("\x03- is a bot");
    if (IsPlayerAlive(iCid)==false)
        PrintToChatAll("\x03- not alive");
    if (GetClientTeam(iCid)!=3)
        PrintToChatAll("\x03- not infected");
    */

    //recount the number of tanks left  获取tank数量
    decl String:st_class[32];
    g_iTankCount=0;
    for (new iI=1 ; iI<=MaxClients ; iI++)
    {
        if (IsClientInGame(iI)==true
            && IsPlayerAlive(iI)==true
            && GetClientTeam(iI)==3)
        {
            GetClientModel(iI,st_class,32);
            if (StrContains(st_class,"hulk",false) != -1)
                g_iTankCount++;

            //----DEBUG----
            //PrintToChatAll("\x03-counting \x01%i",iI);
        }
    }

    //----DEBUG----
    //if (g_iTankCount<=1)
        //PrintToChatAll("\x03- 1 or less tanks");

    //stop the timer if any of these
    //conditions are true
    if (IsClientInGame(iCid)==false
        || IsFakeClient(iCid)==true
        || IsPlayerAlive(iCid)==false
        || GetClientTeam(iCid)!=3
        || g_iTankCount<=1
        || g_iInf2_enable==0
        || g_iDouble_enable==0)
    {
        //----DEBUG----
        //PrintToChatAll("\x03- stopping, tankcount \x01%i",g_iTankCount);

        KillTimer(timer);
        return Plugin_Stop;
    }

    //----DEBUG----
    //PrintToChatAll("\x03- checks passed, tankcount \x01%i",g_iTankCount);

    SetEntData(iCid,g_iFrustrationO,0);

    //----DEBUG----
    //PrintToChatAll("\x03- client \x01%i\x03, current frustration \x01%i", iCid, GetEntData(iCid,g_iFrustrationO) );

    return Plugin_Continue;
}



//when switching players from frustration, reset tank's hp and speed boost
public Action:Event_Tank_Frustrated(Handle:event, const String:name[], bool:dontBroadcast)
{
    //----DEBUG----
    //PrintToChatAll("\x03tank frustration detected");

    new iCid=GetClientOfUserId(GetEventInt(event,"userid"));
    if (IsServerProcessing()==false
        || iCid==0
        || IsClientInGame(iCid)==false
        || GetClientTeam(iCid)!=3)
        return Plugin_Continue;
    // 运行控制检测函数，理论不在发生tank丢控制情况出现
    PrintToChatAll("插件运行出错，请截图给Yd技术一夜男或曼陀罗");
    PrintToChatAll("插件运行出错，请截图给Yd技术一夜男或曼陀罗");
    PrintToChatAll("插件运行出错，请截图给Yd技术一夜男或曼陀罗");

    //if this is a tank spawned under double trouble, it gets no perks
    //setting g_iTank to 4 means any tank "spawns" get no perks
    //and the main tank spawn function won't do anything to the tank
    if (g_iTank==3) g_iTank=4;

    SetEntDataFloat(iCid,g_iLaggedMovementO, 1.0 ,true); // 重置移动速度
    
    return Plugin_Continue;
}

/******************************************************
*    tank循环检测控制权事件
*******************************************************/
public Action:CheckFrustration(Handle:timer, any:client)
{
    if (IsValidPlayer(client, false, false) || (GetClientTeam(client)!=3) || !IsPlayerTank(client)) return;
    new frustration = GetEntProp(client, Prop_Send, "m_frustration");
    if (frustration >= 95)
    {
        // 杀死，2次CD
        clientlosscount[client]++;
        if (clientlosscount[client] >= 2)
        {// 处死
            clientlosscount[client] = 0;
            SetEntProp(client, Prop_Send, "m_frustration", 0);// 重置CD时间
            ForcePlayerSuicide(client);// 处死
            CPrintToChatAll("{olive}因丢失2次Tank控制权，玩家{green}【 %N 】{olive}已被处死", client);
            //ChangeClientTeam(client, 1); // 旁观/
            ///FakeClientCommand(client, "jointeam 3");
            //CreateTimer(0.1, RestoreInfectedTeam, client);  // 再次进入感染者
        }
        else
        {
            CPrintToChatAll("{olive}玩家{green}【 %N 】{olive}丢失Tank控制一次，下次丢失将被直接处死", client);
            SetEntProp(client, Prop_Send, "m_frustration", 0);   // 第二次控制CD重置
            CreateTimer(0.1, CheckFrustration, client);
        }
    }
    else
    {// 循环执行此事件
        CreateTimer(0.1+(95-frustration)*0.1, CheckFrustration, client);
    }
}


//timer to check for bots  机器tank检查
public Action:Timer_TankBot (Handle:timer, any:iTankid)
{
    KillTimer(timer);

    if (IsServerProcessing()==false
        || IsValidEntity(iTankid)==false
        || IsClientInGame(iTankid)==false
        || IsFakeClient(iTankid)==false
        || g_iTankBotTicks>=4
        || g_iInf2_enable==0)
    {
        //----DEBUG----
        //PrintToChatAll("\x03stopping bot timer");

        return Plugin_Stop;
    }

    if (IsClientInGame(iTankid)==true
        && IsFakeClient(iTankid)==true)
    {
        g_iTankBotTicks++;

        //----DEBUG----
        //PrintToChatAll("\x03tankbot tick %i",g_iTankBotTicks);

        if (g_iTankBotTicks>=3)
        {
            //----DEBUG----
            //PrintToChatAll("\x03bot tank detected");

            //set bot perks
            g_iInf2[iTankid] = Bot_Inf2_PickRandom();
            g_iConfirm[iTankid]=1;

            //----DEBUG----
            //PrintToChatAll("\x03-tank bot perk \x01%i",g_iInf2[iTankid]);

            Tank_ApplyPerk(iTankid);
            return Plugin_Stop;
        }
    }
    return Plugin_Stop;
}

//
public bool:IsPlayerTank (client)
{
    new String:class[150];
    GetClientModel(client, class, sizeof(class));
    return (StrContains(class, "hulk", false) != -1);
//    return ((GetClientTeam(client) == 3) && (GetEntProp(client, Prop_Send, "m_zombieClass") == ZC_TANK));
}

public bool:HasPlayerADTank()// 存在暴走tank
{
	for(new i = 1; i <= MAXPLAYERS; i++)
	{
		if(IsValidPlayer(i, true, false) && IsPlayerAlive(i) && IsPlayerTank(i)  && g_iInf2[i] == 1)
		{
			return true;
		}
	}
	return false;
}

//====================================================
//====================================================
//                    M    E    N    U           菜单
//====================================================
//====================================================



//=============================
//    CHAT CHECK, TOP MENU, SELECT SUBMENU
//=============================

//check chat
public Action:MenuOpen_OnSay(iCid, args)
{
    //if (iCid==0) return Plugin_Continue;
    //if (args<1) return Plugin_Continue;
    //decl String:st_chat[MAXPLAYERS+1];
    //GetCmdArg(1,st_chat,64);
    //if (StrEqual(st_chat,"!perks",false)==true)
    //{
    if(!IsValidPlayer(iCid)) return Plugin_Continue;
    
    new iT = GetClientTeam(iCid);

    if (g_iConfirm[iCid]==0)
    {
        if (iT==2)
            SendPanelToClient(Menu_Initial(iCid),iCid,Menu_ChooseInit,MENU_TIME_FOREVER);
        else if (iT==3)
            SendPanelToClient(Menu_Initial(iCid),iCid,Menu_ChooseInit_Inf,MENU_TIME_FOREVER);
        return Plugin_Continue;
    }

    if (iT==2)
        SendPanelToClient(Menu_ShowChoices(iCid),iCid,Menu_DoNothing,15);
    else if (iT==3)
        SendPanelToClient(Menu_ShowChoices_Inf(iCid),iCid,Menu_DoNothing,15);
    //}
    return Plugin_Continue;
}

//build initial menu
public Handle:Menu_Initial (iCid)
{
    new Handle:menu = CreatePanel();
    decl String:stPanel[128];
    SetPanelTitle(menu, "技能系统");

    //"This server is using Perkmod"
    Format(stPanel, 128, "%t", "InitialMenuPanel1");
    DrawPanelText(menu, stPanel);
    //"Select option 1 to customize your perks"
    Format(stPanel, 128, "%t", "InitialMenuPanel2");
    DrawPanelText(menu, stPanel);
    //"Customize Perks"
    Format(stPanel, 128, "%t", "InitialMenuPanel3");
    DrawPanelItem(menu, stPanel);

    //random perks, enable only if cvar is set
    if (g_iRandomEnable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        //"You can opt to randomize your perks"
        Format(stPanel, 128, "%t", "InitialMenuPanel4");
        DrawPanelText(menu, stPanel);
        //"but you can't change them afterwards"
        Format(stPanel, 128, "%t", "InitialMenuPanel5");
        DrawPanelText(menu, stPanel);
        //"Randomize Perks"
        Format(stPanel, 128, "%t", "InitialMenuPanel6");
        DrawPanelItem(menu, stPanel);
    }

    //"Otherwise, you can use whatever"
    Format(stPanel, 128, "%t", "InitialMenuPanel7");
    DrawPanelText(menu, stPanel);
    //"perks you've selected already"
    Format(stPanel, 128, "%t", "InitialMenuPanel8");
    DrawPanelText(menu, stPanel);
    //"by using option 3"
    Format(stPanel, 128, "%t", "InitialMenuPanel9");
    DrawPanelText(menu, stPanel);
    //"PLAY NOW!"
    Format(stPanel, 128, "%t", "InitialMenuPanel10");
    DrawPanelItem(menu, stPanel);

    return menu;
}

public Menu_ChooseInit (Handle:topmenu, MenuAction:action, param1, param2)
{
    if (topmenu!=INVALID_HANDLE) CloseHandle(topmenu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            case 1:
                SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
            case 2:
                {
                    AssignRandomPerks(param1);
                    PrintHintText(param1,"技能系统: %t", "ThanksForChoosingMessage");
                }
            case 3:
                {
                    g_iConfirm[param1]=1;
                    Event_Confirm_Unbreakable(param1);
                    Event_Confirm_Grenadier(param1);
                    Event_Confirm_ChemReliant(param1);
                    Event_Confirm_DT(param1);
                    Event_Confirm_MA(param1);
                    Event_Confirm_LittleLeaguer(param1);
                    PrintHintText(param1,"技能系统: %t", "ThanksForChoosingMessage");
                }
            default:
                {
                    if (IsClientInGame(param1)==true)
                        SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
                }
        }
    }

    else
    {
        if (IsClientInGame(param1)==true)
            SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
    }
}

public Menu_ChooseInit_Inf (Handle:topmenu, MenuAction:action, param1, param2)
{
    if (topmenu!=INVALID_HANDLE) CloseHandle(topmenu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            case 1:
                SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
            case 2:
                {
                    AssignRandomPerks(param1);
                    PrintHintText(param1,"技能系统: %t", "ThanksForChoosingMessage");
                }
            case 3:
                {
                    g_iConfirm[param1]=1;
                    PrintHintText(param1,"技能系统: %t", "ThanksForChoosingMessage");
                }
            default:
                {
                    if (IsClientInGame(param1)==true)
                        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
                }
        }
    }

    else
    {
        if (IsClientInGame(param1)==true)
            SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
    }
}

//build top menu
public Handle:Menu_Top (iCid)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "技能系统");
    DrawPanelText(menu,"请选择主菜单技能");
    decl String:st_perk[32];
    decl String:st_display[MAXPLAYERS+1];

    //set name for sur1 perk
    if (g_iSur1[iCid]==1
        && (g_iStopping_enable==1        &&    g_iL4D_GameMode==0
        || g_iStopping_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iStopping_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="枪神附身";
    else if (g_iSur1[iCid]==2
        && (g_iDT_enable==1        &&    g_iL4D_GameMode==0
        || g_iDT_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iDT_enable_vs==1    &&    g_iL4D_GameMode==2))
        st_perk="夺命速射";
    else if (g_iSur1[iCid]==3
        && (g_iSoH_enable==1        &&    g_iL4D_GameMode==0
        || g_iSoH_enable_sur==1        &&    g_iL4D_GameMode==1
        || g_iSoH_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="极速上弹";
    else if (g_iSur1[iCid]==4
        && (g_iPyro_enable==1        &&    g_iL4D_GameMode==0
        || g_iPyro_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iPyro_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="爆破专家";
    else if (g_iSur1[iCid]==5
        && g_iL4D_12 == 2
        && (g_iMA_enable==1        &&    g_iL4D_GameMode==0
        || g_iMA_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iMA_enable_vs==1    &&    g_iL4D_GameMode==2))
        st_perk="近战专家";
    else
        st_perk="Not set";

    Format(st_display,64,"主技能 (%s)",st_perk);
    if (g_iSur1_enable==1)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);


    //set name for sur2 perk
    if (g_iSur2[iCid]==1
        && (g_iUnbreak_enable==1        &&    g_iL4D_GameMode==0
        || g_iUnbreak_enable_sur==1        &&    g_iL4D_GameMode==1
        || g_iUnbreak_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="医疗专家";
    else if (g_iSur2[iCid]==2
        && (g_iSpirit_enable==1        &&    g_iL4D_GameMode==0
        || g_iSpirit_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iSpirit_enable_vs==1    &&    g_iL4D_GameMode==2))
        st_perk="黄金战甲";
    else if (g_iSur2[iCid]==3
        && (g_iHelpHand_enable==1        &&    g_iL4D_GameMode==0
        || g_iHelpHand_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iHelpHand_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="救护医师";
    else
        st_perk="Not set";

    Format(st_display,64,"副技能 (%s)", st_perk);
    if (g_iSur2_enable==1)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);

    //set name for sur3 perk
    if (g_iSur3[iCid]==1
        && (g_iPack_enable==1        &&    g_iL4D_GameMode==0
        || g_iPack_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iPack_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="弹药专家";
    else if (g_iSur3[iCid]==2
        && (g_iChem_enable==1        &&    g_iL4D_GameMode==0
        || g_iChem_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iChem_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="枪支专家";
    else if (g_iSur3[iCid]==3
        && (g_iHard_enable==1        &&    g_iL4D_GameMode==0
        || g_iHard_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iHard_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="坚韧之躯";
    else if (g_iSur3[iCid]==4
        && (g_iExtreme_enable==1        &&    g_iL4D_GameMode==0
        || g_iExtreme_enable_sur==1        &&    g_iL4D_GameMode==1
        || g_iExtreme_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="凌波微步";
    else if (g_iSur3[iCid]==5
        && (g_iLittle_enable==1            &&    g_iL4D_GameMode==0
        || g_iLittle_enable_sur==1        &&    g_iL4D_GameMode==1
        || g_iLittle_enable_vs==1        &&    g_iL4D_GameMode==2))
        st_perk="棒球队员";
    else
        st_perk="Not set";

    Format(st_display,64,"参技能 (%s)", st_perk);
    if (g_iSur3_enable==1)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);

    DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);
    DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);
    DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);
    DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);
    Format(st_display, 64, "%t", "DoneNagPanel1");    
    DrawPanelText(menu, st_display);
    Format(st_display, 64, "%t", "DoneNagPanel2");    
    DrawPanelText(menu, st_display);
    Format(st_display, 64, "%t", "DoneNagPanel3");    
    DrawPanelItem(menu, st_display);
    return menu;
}

//choose a submenu from top perk menu
public Menu_ChooseSubMenu (Handle:topmenu, MenuAction:action, param1, param2)
{
    if (topmenu!=INVALID_HANDLE) CloseHandle(topmenu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            case 1:
                SendPanelToClient(Menu_Sur1Perk(param1),param1,Menu_ChooseSur1Perk,MENU_TIME_FOREVER);
            case 2:
                SendPanelToClient(Menu_Sur2Perk(param1),param1,Menu_ChooseSur2Perk,MENU_TIME_FOREVER);
            case 3:
                SendPanelToClient(Menu_Sur3Perk(param1),param1,Menu_ChooseSur3Perk,MENU_TIME_FOREVER);
            case 8:
                SendPanelToClient(Menu_Confirm(param1),param1,Menu_ChooseConfirm,MENU_TIME_FOREVER);
            default:
                {
                    if (IsClientInGame(param1)==true)
                        SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
                }
        }
    }

    else
    {
        if (IsClientInGame(param1)==true)
            SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
    }
}


//build top menu,infected
public Handle:Menu_Top_Inf (iCid)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "技能系统");
    DrawPanelText(menu,"请选择主菜单技能");
    decl String:st_perk[32];
    decl String:st_display[MAXPLAYERS+1];
    decl iPerk;

    //set name for inf1 perk
    iPerk = g_iInf1[iCid];
    if (iPerk==1
        && g_iBarf_enable==1)
        st_perk="恶魔唤醒";
    else if (iPerk==2
        && g_iBlind_enable==1)
        st_perk="遗忘之咒";
    else if (iPerk==3
        && g_iDead_enable==1)
        st_perk="致命袭击";
    else if (iPerk==4
        && g_iMotion_enable==1)
        st_perk="身体轻盈";
    else
        st_perk="Not set";

    Format(st_display,64,"Boomer (%s)", st_perk);
    if (g_iInf1_enable==1)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);

    //set name for inf3 perk
    iPerk = g_iInf3[iCid];
    if (iPerk==1
        && g_iTongue_enable==1)
        st_perk="夺命速舌";
    else if (iPerk==2
        && g_iSqueezer_enable==1)
        st_perk="致命之怀";
    else if (iPerk==3
        && g_iDrag_enable==1)
        st_perk="控舌技能";
    else if (iPerk==4
        && g_iSmokeIt_enable==1)
        st_perk="专业拖车";
    else
        st_perk="Not set";

    Format(st_display,64,"Smoker (%s)", st_perk);
    if (g_iInf3_enable==1)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);

    //set name for inf4 perk
    iPerk = g_iInf4[iCid];
    if (iPerk==1
        && g_iBody_enable==1)
        st_perk="鬼影之锤";
    else if (iPerk==2
        && g_iEfficient_enable==1)
        st_perk="夺命杀手";
    else if (iPerk==3
        && g_iGrass_enable==1)
        st_perk="暗黑之忍";
    else if (iPerk==4
        && g_iSpeedDemon_enable==1)
        st_perk="夺命印记";
    else
        st_perk="Not set";

    Format(st_display,64,"Hunter (%s)", st_perk);
    if (g_iInf4_enable==1)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);

    //set name for inf5 perk
    iPerk = g_iInf5[iCid];
    if (iPerk==1
        && g_iWind_enable==1)
        st_perk="专业车手";
    else if (iPerk==2
        && g_iCavalier_enable==1)
        st_perk="血气旺盛";
    else if (iPerk == 3
        && g_iFrogger_enable == 1)
        st_perk = "嗜血之鞍";
    else if (iPerk == 4
        && g_iGhost_enable == 1)
        st_perk = "幽灵幻术";
    else
        st_perk="Not set";

    Format(st_display,64,"Jockey (%s)", st_perk);
    if (g_iInf5_enable==1
        && g_iL4D_12 == 2)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);

    //set name for inf6 perk
    iPerk = g_iInf6[iCid];
    if (iPerk==1
        && g_iTwinSF_enable==1)
        st_perk="五重喷射";
    else if (iPerk==2
        && g_iMegaAd_enable==1)
        st_perk="粘性毒液";
    else
        st_perk="Not set";

    Format(st_display,64,"Spitter (%s)", st_perk);
    if (g_iInf6_enable==1
        && g_iL4D_12 == 2)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);

    //set name for inf7 perk
    iPerk = g_iInf7[iCid];
    if (iPerk==1
        && g_iScatter_enable==1)
        st_perk="战神之力";
    else if (iPerk == 2
        && g_iBullet_enable == 1)
        st_perk = "极速冲击";
    else
        st_perk="Not set";

    Format(st_display,64,"Charger (%s)", st_perk);
    if (g_iInf7_enable==1
        && g_iL4D_12 == 2)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);

    //set name for inf2 perk
    if (g_iInf2[iCid]==1
        && g_iAdrenal_enable==1)
        st_perk="疯狂暴走";
    else if (g_iInf2[iCid]==2
        && g_iJuggernaut_enable==1)
        st_perk="魔王重生";
    else if (g_iInf2[iCid]==3
        && g_iMetabolic_enable==1)
        st_perk="神行百变";
    else if (g_iInf2[iCid]==4
        && g_iStorm_enable==1)
        st_perk="王者召唤";
    else if (g_iInf2[iCid]==5
        && g_iDouble_enable==1)
        st_perk="分身术";
    else
        st_perk="Not set";

    Format(st_display,64,"Tank (%s)", st_perk);
    if (g_iInf2_enable==1)
        DrawPanelItem(menu,st_display);
    else
        DrawPanelItem(menu,st_display, ITEMDRAW_NOTEXT);

    DrawPanelText(menu,"为了使技能生效");
    DrawPanelText(menu,"您必须选择确定");
    DrawPanelItem(menu,"确定");
    return menu;
}

//choose a submenu from top perk menu, infected
public Menu_ChooseSubMenu_Inf (Handle:topmenu, MenuAction:action, param1, param2)
{
    if (topmenu!=INVALID_HANDLE) CloseHandle(topmenu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            case 1:
                SendPanelToClient(Menu_Inf1Perk(param1),param1,Menu_ChooseInf1Perk,MENU_TIME_FOREVER);
            case 2:
                SendPanelToClient(Menu_Inf3Perk(param1),param1,Menu_ChooseInf3Perk,MENU_TIME_FOREVER);
            case 3:
                SendPanelToClient(Menu_Inf4Perk(param1),param1,Menu_ChooseInf4Perk,MENU_TIME_FOREVER);
            case 4:
                SendPanelToClient(Menu_Inf5Perk(param1),param1,Menu_ChooseInf5Perk,MENU_TIME_FOREVER);
            case 5:
                SendPanelToClient(Menu_Inf6Perk(param1),param1,Menu_ChooseInf6Perk,MENU_TIME_FOREVER);
            case 6:
                SendPanelToClient(Menu_Inf7Perk(param1),param1,Menu_ChooseInf7Perk,MENU_TIME_FOREVER);
            case 7:
                SendPanelToClient(Menu_Inf2Perk(param1),param1,Menu_ChooseInf2Perk,MENU_TIME_FOREVER);
            case 8:
                SendPanelToClient(Menu_Confirm(param1),param1,Menu_ChooseConfirm_Inf,MENU_TIME_FOREVER);
            default:
                {
                    if (IsClientInGame(param1)==true)
                        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
                }
        }
    }

    else
    {
        if (IsClientInGame(param1)==true)
            SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
    }
}

//menu for confirming perk choices
public Handle:Menu_Confirm (iCid)
{
    new Handle:menu = CreatePanel();
    decl String:panel[128];
    Format(panel, 128, "%t", "ConfirmNagPanel1");    
    SetPanelTitle(menu, panel);
    
    DrawPanelText(menu,"");
    
    Format(panel, 128, "%t", "ConfirmNagPanel2");
    DrawPanelText(menu,panel);
    Format(panel, 128, "%t", "ConfirmNagPanel3");
    DrawPanelText(menu,panel);
    Format(panel, 128, "%t", "ConfirmNagPanel4");
    DrawPanelItem(menu,panel);
    Format(panel, 128, "%t", "ConfirmNagPanel5");
    DrawPanelText(menu,panel);
    Format(panel, 128, "%t", "ConfirmNagPanel6");
    DrawPanelText(menu,panel);
    Format(panel, 128, "%t", "ConfirmNagPanel7");
    DrawPanelText(menu,panel);
    Format(panel, 128, "%t", "ConfirmNagPanel8");
    DrawPanelItem(menu,panel);
    return menu;
}

//confirm
public Menu_ChooseConfirm (Handle:topmenu, MenuAction:action, param1, param2)
{
    if (topmenu!=INVALID_HANDLE) CloseHandle(topmenu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            case 1:
            {
                g_iConfirm[param1]=1;
                PrintToChat(param1,"\x03[技能] %t", "ConfirmedMessage");
                Event_Confirm_Unbreakable(param1);
                Event_Confirm_Grenadier(param1);
                Event_Confirm_ChemReliant(param1);
                Event_Confirm_DT(param1);
                Event_Confirm_MA(param1);
                Extreme_Rebuild();
                Event_Confirm_LittleLeaguer(param1);
            }
            case 2:
                SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
            default:
            {
                if (IsClientInGame(param1)==true)
                    SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
            }
        }
    }

    else
        SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
}

public Menu_ChooseConfirm_Inf (Handle:topmenu, MenuAction:action, param1, param2)
{
    if (topmenu!=INVALID_HANDLE) CloseHandle(topmenu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            case 1:
            {
                g_iConfirm[param1]=1;
                PrintToChat(param1,"\x03[技能] %t", "ConfirmedMessage");
            }
            case 2:
                SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
            default:
            {
                if (IsClientInGame(param1)==true)
                    SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
            }
        }
    }

    else
        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
}

//do nothing
//for displaying perk choices after confirming
public Menu_DoNothing (Handle:topmenu, MenuAction:action, param1, param2)
{}

//shows perk choices
public Handle:Menu_ShowChoices (iCid)
{
    new Handle:menu=CreatePanel();
    SetPanelTitle(menu,"幸存者已选技能");
    decl String:st_perk[128];
    decl iPerk;
    //"Your perks for this round:"
    Format(st_perk, 128, "%t:", "MapPerksPanel");

    //show sur1 perk
    iPerk = g_iSur1[iCid];
    if (iPerk == 1
        && (g_iStopping_enable==1        &&    g_iL4D_GameMode==0
        || g_iStopping_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iStopping_enable_vs==1        &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"枪神附身 (+%i%% %t)", RoundToNearest(g_flStopping_dmgmult*100), "BonusDamageText_b" );
    else if (iPerk == 2
        && (g_iDT_enable==1        &&    g_iL4D_GameMode==0
        || g_iDT_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iDT_enable_vs==1    &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"夺命速射 (%t +%i%%)", "DoubleTapDescriptionPanel", RoundToNearest(100 * ((1/g_flDT_rate)-1) ) ) ;
    else if (iPerk == 3
        && (g_iSoH_enable==1        &&    g_iL4D_GameMode==0
        || g_iSoH_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iSoH_enable_vs==1    &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"极速上弹(%t +%i%%)", "SleighOfHandDescriptionPanel", RoundToNearest(100 * ((1/g_flSoH_rate)-1) ) ) ;
    else if (iPerk == 4
        && (g_iPyro_enable==1        &&    g_iL4D_GameMode==0
        || g_iPyro_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iPyro_enable_vs==1        &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"爆破专家 (%t)", "PyroDescriptionPanel");
    else if (iPerk == 5
        && (g_iMA_enable==1        &&    g_iL4D_GameMode==0
        || g_iMA_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iMA_enable_vs==1    &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"近战专家 (%t)", "MartialArtistDescriptionPanel");
    else
        Format(st_perk,128,"%t", "NotSet");

    if (g_iSur1_enable==1)
    {
        DrawPanelItem(menu,"主技能:");
        DrawPanelText(menu,st_perk);
    }

    //show sur2 perk
    iPerk = g_iSur2[iCid];
    if (iPerk == 1
        && (g_iUnbreak_enable==1    &&    g_iL4D_GameMode==0
        || g_iUnbreak_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iUnbreak_enable_vs==1    &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"医疗专家 (+%i %t)", g_iUnbreak_hp, "UnbreakableHint");
    else if (iPerk == 2
        && (g_iSpirit_enable==1        &&    g_iL4D_GameMode==0
        || g_iSpirit_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iSpirit_enable_vs==1    &&    g_iL4D_GameMode==2))
    {
        decl iTime;
        if (g_iL4D_GameMode==2)
            iTime=g_iSpirit_cd_vs;
        else if (g_iL4D_GameMode==1)
            iTime=g_iSpirit_cd_sur;
        else
            iTime=g_iSpirit_cd;
        Format(st_perk,128,"黄金战甲 (%t: %i min)", "SpiritDescriptionPanel", iTime/60 );
    }
    else if (iPerk == 3
        && (g_iHelpHand_enable==1        &&    g_iL4D_GameMode==0
        || g_iHelpHand_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iHelpHand_enable_vs==1        &&    g_iL4D_GameMode==2))
    {
        decl iBuff;
        if (g_iL4D_GameMode==2)
            iBuff=g_iHelpHand_buff_vs;
        else
            iBuff=g_iHelpHand_buff;
        if (g_iHelpHand_convar==1)
            Format(st_perk,128,"救护医师 (%t +%i)", "HelpingHandDescriptionPanel2", iBuff);
        else
            Format(st_perk,128,"救护医师 (%t +%i)", "HelpingHandDescriptionPanel", iBuff);
    }
    else
        Format(st_perk,128,"%t", "NotSet");

    if (g_iSur2_enable==1)
    {
        DrawPanelItem(menu,"副技能:");
        DrawPanelText(menu,st_perk);
    }

    //show sur3 perk
    iPerk = g_iSur3[iCid];
    if (iPerk == 1
        && (g_iPack_enable==1        &&    g_iL4D_GameMode==0
        || g_iPack_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iPack_enable_vs==1        &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"弹药专家 (%t +%i%%)", "PackRatDescriptionPanel", RoundToNearest(g_flPack_ammomult*100) );
    else if (iPerk == 2
        && (g_iChem_enable==1        &&    g_iL4D_GameMode==0
        || g_iChem_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iChem_enable_vs==1        &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"枪支专家 (%t +%i)", "ChemReliantDescriptionPanel", g_iChem_buff);
    else if (iPerk == 3
        && (g_iHard_enable==1            &&    g_iL4D_GameMode==0
        || g_iHard_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iHard_enable_vs==1        &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"坚韧之躯 (+%i%% %t)", RoundToNearest(g_flHard_hpmult*100), "HardToKillDescriptionPanel" );
    else if (iPerk == 4
        && (g_iExtreme_enable==1        &&    g_iL4D_GameMode==0
        || g_iExtreme_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iExtreme_enable_vs==1    &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"凌波微步 (+%i%% %t)", RoundToNearest(g_flExtreme_rate*100-100), "MartialArtistDescriptionPanelCoop" );
    else if (iPerk == 5
        && (g_iLittle_enable==1        &&    g_iL4D_GameMode==0
        || g_iLittle_enable_sur==1    &&    g_iL4D_GameMode==1
        || g_iLittle_enable_vs==1    &&    g_iL4D_GameMode==2))
        Format(st_perk,128,"棒球队员 (%t)", "LittleLeaguerDescriptionPanel" );
    else
        Format(st_perk,128,"%t", "NotSet");

    if (g_iSur2_enable==1)
    {
        DrawPanelItem(menu,"参技能:");
        DrawPanelText(menu,st_perk);
    }

    return menu;
}



//shows perk choices, infected
public Handle:Menu_ShowChoices_Inf (iCid)
{
    new Handle:menu=CreatePanel();
    SetPanelTitle(menu,"感染者已选技能");
    decl String:st_perk[128];
    decl String:stDesc[128];
    decl iPerk;

    //show inf1 perk
    iPerk = g_iInf1[iCid];
    if (iPerk == 1
        && g_iBarf_enable == 1)
    {
        st_perk="Boomer: 恶魔唤醒";
        Format(stDesc,128,"%t", "BarfBaggedDescriptionPanel");
    }
    else if (iPerk == 2
        && g_iBlind_enable == 1)
    {
        st_perk="Boomer: 遗忘之咒";
        Format(stDesc,128,"%t", "AcidVomitDescriptionPanel");
    }
    else if (iPerk == 3
        && g_iDead_enable == 1)
    {
        st_perk="Boomer: 致命袭击";
        Format(stDesc,128,"%t: +%i%%", "DeadWreckeningDescriptionPanel", RoundToNearest(100*g_flDead_dmgmult));
    }
    else if (iPerk == 4
        && g_iMotion_enable == 1)
    {
        st_perk="Boomer: 身体轻盈";
        Format(stDesc,128,"%t", "MotionSicknessDescriptionPanel");
    }
    else
    {
        Format(st_perk,128,"Boomer: %t", "NotSet");
        stDesc = "";
    }

    if (g_iInf1_enable==1)
    {
        DrawPanelItem(menu,st_perk);
        DrawPanelText(menu,stDesc);
    }

    //show inf3 perk
    iPerk = g_iInf3[iCid];
    if (iPerk == 1
        && g_iTongue_enable == 1)
    {
        st_perk="Smoker: 夺命速舌";
        Format(stDesc,128,"%t", "TongueTwisterDescriptionPanel");
    }
    else if (iPerk == 2
        && g_iSqueezer_enable == 1)
    {
        st_perk="Smoker: 致命之怀";
        Format(stDesc,128,"+%i%% %t", RoundToNearest(g_flSqueezer_dmgmult*100), "BonusDamageText_d" );
    }
    else if (iPerk == 3
        && g_iDrag_enable == 1)
    {
        st_perk="Smoker: 控舌";
        Format(stDesc,128,"%t", "DragAndDropDescriptionPanel");
    }
    else if (iPerk == 4
        && g_iSmokeIt_enable == 1)
    {
        st_perk="Smoker: 专业拖车";
        Format(stDesc,128,"%t", "SmokeItDescriptionPanel");
    }
    else
    {
        Format(st_perk,128,"Smoker: %t", "NotSet");
        stDesc = "";
    }

    if (g_iInf3_enable==1)
    {
        DrawPanelItem(menu,st_perk);
        DrawPanelText(menu,stDesc);
    }

    //show inf4 perk
    iPerk = g_iInf4[iCid];
    if (iPerk == 1
        && g_iBody_enable == 1)
    {
        st_perk="Hunter: 鬼影之锤";
        Format(stDesc,128,"%i %t", g_iBody_minbound, "BodySlamDescriptionPanel");
    }
    else if (iPerk == 2
        && g_iEfficient_enable == 1)
    {
        st_perk="Hunter: 夺命杀手";
        Format(stDesc,128,"+%i%% %t", RoundToNearest(g_flEfficient_dmgmult*100), "BonusDamageText_c" );
    }
    else if (iPerk == 3
        && g_iGrass_enable == 1)
    {
        st_perk="Hunter: 暗黑之忍";
        Format(stDesc,128,"%t: +%i%%", "GrasshopperDescriptionPanel", RoundToNearest( (g_flGrass_rate - 1) * 100 ) );
    }
    else if (iPerk == 4
        && g_iSpeedDemon_enable == 1)
    {
        st_perk="Hunter: 夺命印记";
        Format(stDesc,128,"+%i%% %t +%i%% %t", RoundToNearest(g_flSpeedDemon_dmgmult*100), "OldSchoolDescriptionPanel", RoundToNearest( (g_flSpeedDemon_rate - 1) * 100 ), "SpeedDemonDescriptionPanel" );
    }
    else
    {
        Format(st_perk,128,"Hunter: %t", "NotSet");
        stDesc = "";
    }
    if (g_iInf4_enable==1)
    {
        DrawPanelItem(menu,st_perk);
        DrawPanelText(menu,stDesc);
    }

    //show inf5 perk
    iPerk = g_iInf5[iCid];
    if (iPerk == 1
        && g_iWind_enable == 1)
    {
        st_perk="Jockey: 专业车手";
        Format(stDesc,128,"%t: +%i%%", "RideLikeTheWindDescriptionPanel", RoundToNearest( (g_flWind_rate - 1) * 100 ) );
    }
    else if (iPerk == 2
        && g_iCavalier_enable == 1)
    {
        st_perk = "Jockey: 血气旺盛";
        Format(stDesc,128,"+%i%% %t", RoundToNearest( g_flCavalier_hpmult * 100 ), "UnbreakableHint_b" );
    }
    else if (iPerk == 3
        && g_iFrogger_enable == 1)
    {
        st_perk = "Jockey: 嗜血之鞍";
        Format(stDesc, 128, "+%i%% %t +%i%% %t", RoundToNearest( (g_flFrogger_rate - 1) * 100 ), "FroggerDescriptionPanel", RoundToNearest(g_flFrogger_dmgmult*100), "BonusDamageText" );
    }
    else if (iPerk == 4
        && g_iGhost_enable == 1)
    {
        st_perk = "Jockey: 幽灵幻术";
        Format(stDesc, 128, "%i%% %t", RoundToNearest( (1 - (g_iGhost_alpha/255.0)) *100 ), "GhostRiderDescriptionPanel" );
    }
    else
    {
        Format(st_perk,128,"Jockey: %t", "NotSet");
        stDesc = "";
    }

    if (g_iInf5_enable==1
        && g_iL4D_12 == 2)
    {
        DrawPanelItem(menu,st_perk);
        DrawPanelText(menu,stDesc);
    }

    //show inf6 perk
    iPerk = g_iInf6[iCid];
    if (iPerk == 1
        && g_iTwinSF_enable == 1)
    {
        st_perk="Spitter: 五重喷射";
        Format(stDesc, 128, "%t", "TwinSpitfireDescriptionPanel" );
    }
    if (iPerk == 2
        && g_iMegaAd_enable == 1)
    {
        st_perk="Spitter: 粘性毒液";
        Format(stDesc, 128, "%t", "MegaAdhesiveDescriptionPanel" );
    }
    else
    {
        Format(st_perk,128,"Spitter: %t", "NotSet");
        stDesc="";
    }

    if (g_iInf6_enable==1
        && g_iL4D_12 == 2)
    {
        DrawPanelItem(menu,st_perk);
        DrawPanelText(menu,stDesc);
    }

    //show inf7 perk
    iPerk = g_iInf7[iCid];
    if (iPerk == 1
        && g_iScatter_enable == 1)
    {
        st_perk="Charger: 战神之力";
        Format(stDesc, 128, "%t", "ScatteringRamDescriptionPanel" );
    }
    else if (iPerk == 2
        && g_iBullet_enable == 1)
    {
        st_perk = "Charger: 极速冲击";
        Format(stDesc, 128, "%t: +%i%%", "SpeedingBulletDescriptionPanel", RoundToNearest(g_flBullet_rate*100 - 100) );
    }
    else
    {
        Format(st_perk,128,"Charger: %t", "NotSet");
        stDesc="";
    }
    if (g_iInf7_enable==1
        && g_iL4D_12 == 2)
    {
        DrawPanelItem(menu,st_perk);
        DrawPanelText(menu,stDesc);
    }
    
    //show inf2 perk
    iPerk = g_iInf2[iCid];
    if (iPerk == 1
        && g_iAdrenal_enable == 1)
    {
        st_perk="Tank: 疯狂暴走";
        Format(stDesc,128,"%t", "AdrenalGlandsDescriptionPanelShort");
    }
    else if (iPerk == 2
        && g_iJuggernaut_enable == 1)
    {
        st_perk="Tank: 魔王重生";
        Format(stDesc,128,"+%i %t", g_iJuggernaut_hp, "UnbreakableHint");
    }
    else if (iPerk == 3
        && g_iMetabolic_enable == 1)
    {
        st_perk="Tank: 神行百变";
        Format(stDesc,128,"+%i%% %t", RoundToNearest((g_flMetabolic_speedmult-1)*100), "SpeedDemonDescriptionPanel_2");
    }
    else if (iPerk == 4
        && g_iStorm_enable == 1)
    {
        st_perk="Tank: 王者召唤";
        Format(stDesc,128,"%t", "StormCallerDescriptionPanel");
    }
    else if (iPerk == 5
        && g_iDouble_enable == 1)
    {
        st_perk="Tank: 分身术";
        Format(stDesc,128,"%t", "DoubleTroubleDescriptionPanel");
    }
    else
    {
        Format(st_perk,128,"Tank: %t", "NotSet");
        stDesc = "";
    }

    if (g_iInf2_enable)
    {
        DrawPanelItem(menu,st_perk);
        DrawPanelText(menu,stDesc);
    }

    return menu;
}



//=============================
//    SUR1 PERK CHOICE        幸存第一种技能选择
//=============================

//build menu for Sur1 Perks
public Handle:Menu_Sur1Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "幸存者 - 主技能");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iStopping_enable==0            &&    g_iL4D_GameMode==0
        || g_iStopping_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iStopping_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur1[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"枪神附身 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"+%i%% %t", RoundToNearest(g_flStopping_dmgmult*100), "BonusDamageText_b" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iDT_enable==0            &&    g_iL4D_GameMode==0
        || g_iDT_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iDT_enable_vs==0    &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur1[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"夺命速射 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t +%i%%", "DoubleTapDescriptionPanel", RoundToNearest(100 * ((1/g_flDT_rate)-1) ) );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 3
    if (g_iSoH_enable==0            &&    g_iL4D_GameMode==0
        || g_iSoH_enable_sur==0        &&    g_iL4D_GameMode==1
        || g_iSoH_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur1[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"极速上弹 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t +%i%%", "SleighOfHandDescriptionPanel", RoundToNearest(100 * ((1/g_flSoH_rate)-1) ) );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 4
    if (g_iPyro_enable==0            &&    g_iL4D_GameMode==0
        || g_iPyro_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iPyro_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur1[client])
        {
            case 4: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"爆破专家 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t", "PyroDescriptionText1");
        DrawPanelText(menu, st_display);
        Format(st_display,64,"%t", "PyroDescriptionText2");
        DrawPanelText(menu, st_display);
    }

    //set name for perk 5
    if (g_iMA_enable==0            &&    g_iL4D_GameMode==0
        || g_iMA_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iMA_enable_vs==0    &&    g_iL4D_GameMode==2
        || g_iL4D_12 != 2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur1[client])
        {
            case 5: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"近战专家 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t", "MartialArtistDescriptionPanel1");
        DrawPanelText(menu, st_display);
        Format(st_display,64,"%t", "MartialArtistDescriptionPanel2");
        DrawPanelText(menu, st_display);
    }

    return menu;
}

//setting Sur1 perk and returning to top menu
public Menu_ChooseSur1Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //stopping power
            case 1:
                g_iSur1[param1]=1;
            //double tap
            case 2:
                g_iSur1[param1]=2;
            //sleight of hand
            case 3:
                g_iSur1[param1]=3;
            //pyrotechnician
            case 4:
                g_iSur1[param1]=4;
            //martial artist
            case 5:
                g_iSur1[param1]=5;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
}



//=============================
//    SUR2 CHOICE
//=============================

//build menu for Sur2 Perks
public Handle:Menu_Sur2Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "幸存者 - 副技能");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iUnbreak_enable==0            &&    g_iL4D_GameMode==0
        || g_iUnbreak_enable_sur==0        &&    g_iL4D_GameMode==1
        || g_iUnbreak_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur2[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"医疗专家 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"+%i %t", g_iUnbreak_hp, "UnbreakableHint" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iSpirit_enable==0            &&    g_iL4D_GameMode==0
        || g_iSpirit_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iSpirit_enable_vs==0    &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur2[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"黄金战甲 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t", "SpiritDescriptionText" );
        DrawPanelText(menu,st_display);
        decl iTime;
        if (g_iL4D_GameMode==2)
            iTime=g_iSpirit_cd_vs;
        else if (g_iL4D_GameMode==1)
            iTime=g_iSpirit_cd_sur;
        else
            iTime=g_iSpirit_cd;
        Format(st_display,64,"+%i %t: %i 分钟", g_iSpirit_buff, "SpritDescriptionText2", iTime/60 );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 3
    if (g_iHelpHand_enable==0            &&    g_iL4D_GameMode==0
        || g_iHelpHand_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iHelpHand_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur2[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"救护医师 %s",st_current);
        DrawPanelItem(menu,st_display);

        decl iBuff;
        if (g_iL4D_GameMode==2)
            iBuff=g_iHelpHand_buff_vs;
        else
            iBuff=g_iHelpHand_buff;

        if (g_iHelpHand_convar==1)
        {
            Format(st_display,64,"%t +%i", "HelpingHandDescriptionPanel2", iBuff);
            DrawPanelText(menu,st_display);
        }
        else
        {
            Format(st_display,64,"%t +%i", "HelpingHandDescriptionPanel", iBuff);
            DrawPanelText(menu,st_display);
        }
    }

    return menu;
}

//setting Sur2 perk and returning to top menu
public Menu_ChooseSur2Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //unbreakable
            case 1:
                g_iSur2[param1]=1;
            //spirit
            case 2:
                g_iSur2[param1]=2;
            //helping hand
            case 3:
                g_iSur2[param1]=3;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
}



//=============================
//    SUR3 CHOICE
//=============================

//build menu for Sur3 Perks
public Handle:Menu_Sur3Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "幸存者 - 参技能");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iPack_enable==0            &&    g_iL4D_GameMode==0
        || g_iPack_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iPack_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur3[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"弹药专家 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t +%i%%", "PackRatDescriptionPanel", RoundToNearest(g_flPack_ammomult*100) );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iChem_enable==0            &&    g_iL4D_GameMode==0
        || g_iChem_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iChem_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur3[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"枪支专家 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t (+2)", "ChemReliantDescriptionText", g_iChem_buff);
        DrawPanelText(menu,st_display);
        Format(st_display,64,"%t", "ChemReliantDescriptionText2");
        DrawPanelText(menu,st_display);
    }

    //set name for perk 3
    if (g_iHard_enable==0            &&    g_iL4D_GameMode==0
        || g_iHard_enable_sur==0    &&    g_iL4D_GameMode==1
        || g_iHard_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur3[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"坚韧之躯 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t", "HardToKillDescriptionText");
        DrawPanelText(menu,st_display);
        Format(st_display,64,"+%i%% %t", RoundToNearest(100*g_flHard_hpmult), "HardToKillDescriptionText2" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 4
    if (g_iExtreme_enable==0            &&    g_iL4D_GameMode==0
        || g_iExtreme_enable_sur==0        &&    g_iL4D_GameMode==1
        || g_iExtreme_enable_vs==0        &&    g_iL4D_GameMode==2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur3[client])
        {
            case 4: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"凌波微步 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "MartialArtistDescriptionPanelCoop", RoundToNearest(100*g_flExtreme_rate-100) );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 5
    if (g_iLittle_enable==0                &&    g_iL4D_GameMode==0
        || g_iLittle_enable_sur==0        &&    g_iL4D_GameMode==1
        || g_iLittle_enable_vs==0        &&    g_iL4D_GameMode==2
        || g_iL4D_12 != 2)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iSur3[client])
        {
            case 4: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"棒球队员 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t", "LittleLeaguerDescriptionPanel" );
        DrawPanelText(menu,st_display);
    }

    return menu;
}

//setting Sur3 perk and returning to top menu
public Menu_ChooseSur3Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //pack rat
            case 1:
                g_iSur3[param1]=1;
            //chem reliant
            case 2:
                g_iSur3[param1]=2;
            //hard to kill
            case 3:
                g_iSur3[param1]=3;
            //extreme cond
            case 4:
                g_iSur3[param1]=4;
            //little leaguer
            case 5:
                g_iSur3[param1]=5;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top(param1),param1,Menu_ChooseSubMenu,MENU_TIME_FOREVER);
}



//=============================
//    INF1 CHOICE (BOOMER)
//=============================

//build menu for Inf1 Perks
public Handle:Menu_Inf1Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "感染者 - Boomer");
    decl String:st_display[128];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iBarf_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf1[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"恶魔唤醒 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,128,"%t", "BarfBaggedDescriptionPanel");
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iBlind_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf1[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"遗忘之咒 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,128,"%t: %i%%", "AcidVomitDescriptionPanel", RoundToNearest(100 - g_flBlind_cdmult*100) );
        DrawPanelText(menu,st_display);
        DrawPanelText(menu,"我们的书总是残破不堪");
    }

    //set name for perk 3
    if (g_iDead_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf1[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"致命袭击 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,128,"%t: +%i%%", "DeadWreckeningDescriptionPanel", RoundToNearest(100*g_flDead_dmgmult));
        DrawPanelText(menu,st_display);
        DrawPanelText(menu,"有可能会看见吐出的假牙...");
    }

    //set name for perk 4
    if (g_iMotion_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf1[client])
        {
            case 4: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"身体轻盈 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,128,"%t", "MotionSicknessDescriptionPanel");
        DrawPanelText(menu,st_display);
    }

    return menu;
}

//setting Inf1 perk and returning to top menu
public Menu_ChooseInf1Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //barf bagged
            case 1:
                g_iInf1[param1]=1;
            //blind luck
            case 2:
                g_iInf1[param1]=2;
            //dead wreckening
            case 3:
                g_iInf1[param1]=3;
            //motion sickness
            case 4:
                g_iInf1[param1]=4;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
}



//=============================
//    INF2 CHOICE (TANK)
//=============================

//build menu for Inf2 Perks
public Handle:Menu_Inf2Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "感染者 - Tank");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iAdrenal_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf2[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"疯狂暴走 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "AdrenalGlandsDescriptionPanel1", RoundToNearest(100 * ((1/g_flAdrenal_punchcdmult)-1) ) );
        DrawPanelText(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "AdrenalGlandsDescriptionPanel2", RoundToNearest(100 - 100*g_flAdrenal_throwcdmult ) );
        DrawPanelText(menu,st_display);
        Format(st_display,64,"%t", "AdrenalGlandsDescriptionPanel3" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iJuggernaut_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf2[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"魔王重生 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,128,"+%i %t", g_iJuggernaut_hp, "UnbreakableHint");
        DrawPanelText(menu,st_display);
    }

    //set name for perk 3
    if (g_iMetabolic_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf2[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"神行百变 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,128,"+%i%% %t", RoundToNearest((g_flMetabolic_speedmult-1)*100), "SpeedDemonDescriptionPanel_2");
        DrawPanelText(menu,st_display);
    }

    //set name for perk 4
    if (g_iStorm_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf2[client])
        {
            case 4: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"王者召唤 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,128,"%t", "StormCallerDescriptionPanel");
        DrawPanelText(menu,st_display);
    }

    //set name for perk 5
    if (g_iDouble_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf2[client])
        {
            case 5: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"分身术 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,128,"%t", "DoubleTroubleDescriptionPanel");
        DrawPanelText(menu,st_display);
        Format(st_display,128,"%t: -%i%%", "DoubleTroubleDescriptionPanel2", RoundToNearest(100 - g_flDouble_hpmult*100));
        DrawPanelText(menu,st_display);
    }

    return menu;
}

//setting Inf2 perk and returning to top menu
public Menu_ChooseInf2Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //adrenal glands
            case 1:
                g_iInf2[param1]=1;
            //juggernaut
            case 2:
                g_iInf2[param1]=2;
            //metabolic boost
            case 3:
                g_iInf2[param1]=3;
            //storm caller
            case 4:
                g_iInf2[param1]=4;
            //double the trouble
            case 5:
                g_iInf2[param1]=5;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
}



//=============================
//    INF3 CHOICE (SMOKER)
//=============================

//build menu for Inf3 Perks
public Handle:Menu_Inf3Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "感染者 - Smoker");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iTongue_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf3[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"夺命速舌 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "TongueTwisterDescriptionPanel1", RoundToNearest(100*(g_flTongue_speedmult-1)) );
        DrawPanelText(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "TongueTwisterDescriptionPanel2", RoundToNearest(100*(g_flTongue_rangemult-1)) );
        DrawPanelText(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "TongueTwisterDescriptionPanel3", RoundToNearest(100*(g_flTongue_pullmult-1)) );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iSqueezer_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf3[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"致命之怀 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "SqueezerDescriptionText", RoundToNearest(g_flSqueezer_dmgmult*100) );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 3
    if (g_iDrag_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf3[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"控舌 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t", "DragAndDropDescriptionPanel" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 4
    if (g_iSmokeIt_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf3[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"专业拖车 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t", "SmokeItDescriptionPanel" );
        DrawPanelText(menu,st_display);
    }

    return menu;
}

//setting Inf3 perk and returning to top menu
public Menu_ChooseInf3Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //tongue twister
            case 1:
                g_iInf3[param1]=1;
            //squeezer
            case 2:
                g_iInf3[param1]=2;
            //drag and drop
            case 3:
                g_iInf3[param1]=3;
            //smoke it!
            case 4:
                g_iInf3[param1]=4;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
}



//=============================
//    INF4 CHOICE (HUNTER)
//=============================

//build menu for Inf4 Perks
public Handle:Menu_Inf4Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "感染者 - Hunter");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iBody_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf4[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"鬼影之锤 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t %i", "BodySlamDescriptionPanel", g_iBody_minbound);
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iEfficient_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf4[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"夺命杀手 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"+%i%% %t", RoundToNearest(g_flEfficient_dmgmult*100), "BonusDamageText_c" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 3
    if (g_iGrass_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf4[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"暗黑之忍 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "GrasshopperDescriptionPanel", RoundToNearest( (g_flGrass_rate - 1) * 100 ) );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 4
    if (g_iSpeedDemon_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf4[client])
        {
            case 4: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"夺命印记 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"+%i%% %t +%i%% %t", RoundToNearest(g_flSpeedDemon_dmgmult*100), "OldSchoolDescriptionPanel", RoundToNearest( (g_flSpeedDemon_rate - 1) * 100 ), "SpeedDemonDescriptionPanel" );
        DrawPanelText(menu,st_display);
    }

    return menu;
}

//setting Inf4 perk and returning to top menu
public Menu_ChooseInf4Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //body slam
            case 1:
                g_iInf4[param1]=1;
            //efficient killer
            case 2:
                g_iInf4[param1]=2;
            //grasshopper
            case 3:
                g_iInf4[param1]=3;
            //speed demon
            case 4:
                g_iInf4[param1]=4;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
}



//=============================
//    INF5 CHOICE (JOCKEY)
//=============================

//build menu for Inf5 Perks
public Handle:Menu_Inf5Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "感染者 - Jockey");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iWind_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf5[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"专业车手 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "RideLikeTheWindDescriptionPanel", RoundToNearest( (g_flWind_rate - 1) * 100 ) );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iCavalier_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf5[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"血气旺盛 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"+%i%% %t", RoundToNearest( g_flCavalier_hpmult * 100 ), "UnbreakableHint_b" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 3
    if (g_iFrogger_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf5[client])
        {
            case 3: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"嗜血之鞍 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"+%i%% %t +%i%% %t", RoundToNearest( (g_flFrogger_rate - 1) * 100 ), "FroggerDescriptionPanel", RoundToNearest(g_flFrogger_dmgmult*100), "BonusDamageText" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 4
    if (g_iGhost_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf5[client])
        {
            case 4: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"幽灵幻术 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%i%% %t", RoundToNearest( (1 - (g_iGhost_alpha/255.0)) *100 ), "GhostRiderDescriptionPanel" );
        DrawPanelText(menu,st_display);
    }

    return menu;
}

//setting Inf5 perk and returning to top menu
public Menu_ChooseInf5Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //ride like the wind
            case 1:
                g_iInf5[param1]=1;
            //cavalier
            case 2:
                g_iInf5[param1]=2;
            //frogger
            case 3:
                g_iInf5[param1]=3;
            //ghost
            case 4:
                g_iInf5[param1]=4;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
}



//=============================
//    INF6 CHOICE (SPITTER)
//=============================

//build menu for Inf6 Perks
public Handle:Menu_Inf6Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "感染者 - Spitter");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iTwinSF_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf6[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"五重喷射 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64, "%t", "TwinSpitfireDescriptionPanel" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iMegaAd_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf6[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"粘性毒液 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64, "%t: %i%%", "MegaAdhesiveDescriptionPanel", RoundToNearest( 100 - (g_flMegaAd_slow) * 100 ) );
        DrawPanelText(menu,st_display);
    }

    return menu;
}

//setting Inf5 perk and returning to top menu
public Menu_ChooseInf6Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //twin spitfire
            case 1:
                g_iInf6[param1]=1;
            //mega adhesive
            case 2:
                g_iInf6[param1]=2;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
}



//=============================
//    INF7 CHOICE (CHARGER)
//=============================

//build menu for Inf7 Perks
public Handle:Menu_Inf7Perk (client)
{
    new Handle:menu = CreatePanel();
    SetPanelTitle(menu, "感染者 - Charger");
    decl String:st_display[MAXPLAYERS+1];
    decl String:st_current[10];

    //set name for perk 1
    if (g_iScatter_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf7[client])
        {
            case 1: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"战神之力 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"+%i%% %t", RoundToNearest(g_flScatter_hpmult*100), "ScatteringRamDescriptionPanel" );
        DrawPanelText(menu,st_display);
    }

    //set name for perk 2
    if (g_iBullet_enable==0)
    {
        DrawPanelItem(menu,"disabled", ITEMDRAW_NOTEXT);
    }
    else
    {
        switch (g_iInf7[client])
        {
            case 2: st_current="(已选)";
            default: st_current="";
        }
        Format(st_display,64,"极速冲击 %s",st_current);
        DrawPanelItem(menu,st_display);
        Format(st_display,64,"%t: +%i%%", "SpeedingBulletDescriptionPanel", RoundToNearest(g_flBullet_rate*100 - 100) );
        DrawPanelText(menu,st_display);
    }

    return menu;
}

//setting Inf7 perk and returning to top menu
public Menu_ChooseInf7Perk (Handle:menu, MenuAction:action, param1, param2)
{
    if (menu!=INVALID_HANDLE) CloseHandle(menu);
    if (action==MenuAction_Select)
    {
        switch(param2)
        {
            //scatter
            case 1:
                g_iInf7[param1]=1;
            //bullet
            case 2:
                g_iInf7[param1]=2;
        }
    }

    if (IsClientInGame(param1)==true)
        SendPanelToClient(Menu_Top_Inf(param1),param1,Menu_ChooseSubMenu_Inf,MENU_TIME_FOREVER);
}

/* 判断玩家是否有效 */
stock bool:IsValidPlayer(Client, bool:AllowBot = true, bool:AllowDeath = true)
{
    if (Client < 1 || Client > MaxClients)
        return false;
    if (!IsClientConnected(Client) || !IsClientInGame(Client))
        return false;
    if (!AllowBot)
    {
        if (IsFakeClient(Client))
            return false;
    }

    if (!AllowDeath)
    {
        if (!IsPlayerAlive(Client))
            return false;
    }    
    
    return true;
}
stock SetEntityHealthEx(client, maxhealth)
{
	SetEntProp(client, Prop_Data, "m_iMaxHealth", maxhealth);
    SetEntProp(client, Prop_Data, "m_iHealth", maxhealth);
}

stock ResetInfectedAbility(client, Float:time)
{
	if (client > 0)
	{
		if (IsClientInGame(client) && IsPlayerAlive(client) && GetClientTeam(client) == 3)
		{
			new ability = GetEntPropEnt(client, Prop_Send, "m_customAbility");
			if (ability > 0)
			{
				SetEntPropFloat(ability, Prop_Send, "m_duration", time);
				SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + time);
			}
		}
	}
}

